Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: extending with rustup components and external subcommands #31

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
363 changes: 355 additions & 8 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 17 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ path = "src/bin/cargo-build-all-features.rs"
name = "cargo-test-all-features"
path = "src/bin/cargo-test-all-features.rs"


[[bin]]
name = "cargo-check-all-features"
path = "src/bin/cargo-check-all-features.rs"

[[bin]]
name = "cargo-all-features"
path = "src/bin/cargo-all-features.rs"

[dependencies]
json = "0.12"
itertools = "0.10"
termcolor = "1"
itertools = "0.10.3"
serde_json = "1.0.79"
serde = { version = "1.0.136", features = ["derive"] }
rayon = "1.5.2"
lazy_static = "1.4.0"
which = "4.2.5"
clap = { version = "3.1.9", features = ["derive", "cargo"] }
yansi = "0.5.1"
82 changes: 77 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,93 @@ The following commands can be run within a Cargo package or at the root of a Car
Build crate with all feature flag combinations:

```
cargo build-all-features <CARGO BUILD FLAGS>
cargo all-features build -- <CARGO BUILD FLAGS>
```

Check crate with all feature flag combinations:

```
cargo check-all-features <CARGO CHECK FLAGS>
cargo all-features check -- <CARGO CHECK FLAGS>
```

Test crate with all feature flag combinations:

```
cargo test-all-features <CARGO TEST FLAGS>
cargo all-features test -- <CARGO TEST FLAGS>
```

<details>
<summary markdown="title"><bold>Supported tools</bold></summary>

- First party
- [`cargo test`](https://doc.rust-lang.org/cargo/commands/cargo-test.html) cargos integrated testing tool
- [`cargo check`](https://doc.rust-lang.org/cargo/commands/cargo-check.html) cargos integrated checking tool
- [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) cargos integrated build tool
- `cargo bench` [Used by cargos benching feature](https://doc.rust-lang.org/cargo/commands/cargo-bench.html) or crates like [citerion](https://github.com/bheisler/criterion.rs)
- Additional RustUp components
- [`cargo miri test`](https://github.com/rust-lang/miri) for testing using miri -> _rustup component `miri` is needed_
- Cargo plugins
- [`cargo udeps`](https://github.com/est31/cargo-udeps) to analyze for unused dependencies -> _cargo plugin `cargo-udeps` is needed_
- [`cargo tarpaulin`](https://github.com/xd009642/tarpaulin) to analyze for unused dependencies -> _cargo plugin `cargo-tarpaulin` is needed_
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tarpaulin is a code coverage reporting tool, and seems like not a unused dependencies analyzer:

Tarpaulin is a code coverage reporting tool for the Cargo build system, named for a waterproof cloth used to cover cargo on a ship. Currently, tarpaulin provides working line coverage and while fairly reliable may still contain minor inaccuracies in the results. A lot of work has been done to get it working on a wide range of projects, but often unique combinations of packages and build features can cause issues so please report anything you find that's wrong. Also, check out our roadmap for planned features.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sry, yes totally missed that copy paste error, thanks

- [`cargo nextest`](https://nexte.st/) the next generation test runner for cargo -> _cargo plugin `cargo-nextest` is needed_

> for more information run `cargo all-features --help`
</details>

<details>
<summary markdown="span">Additional Features</summary>

### Chunking

If certain projects, features might add up and CI jobs can take longer. In order to shrink wall time of your builds you can specify `--chunks` (the total amount of junks to split into _[1..]_) and `--chunk` (the chunk nr of the one executed command _\[1..\<CHUNKS\>\]_) per execution.

I.e. in github you can use a job matrix:

```yaml
name: CI

on: [pull_request]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
chunk: [1,2,3,4]
chunks: 4
steps:
- uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Install cargo-all-features
uses: actions-rs/cargo@v1
with:
command: install cargo-all-features
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should suggest passing the argument that specifies a version to install. That could make it easier to introduce breaking changes in the future 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a fan of the idea, but I do not really think many users will manually change the version inside the commands input as this would not be covered by something like dependabot.

One suggestion would be create a gh action for cargo-all-features itself, this could mean a 1:1 mapping between gh-action version and crate version which then could be updated using dependabot.

- name: Build all features for release
run: cargo all-features build --chunks ${{matrix.chunks}} --chunk ${{matrix.chunk}} -- --release
```

### Dry run & Verbosity

You are not sure if you configured something correct but don't have the time to wait for all tests or builds? Use `--dry-run`, it will skip all command execution.

If you are not sure if the correct command are executed use `--verbose`

### RustUp toolchain

Don't mind to use `+<toolchain>` or any other combination of rustups toolchain selection. `cargo-all-features` will pick up on the active toolchain and use it.

> for more information run `cargo all-features --help`

### Cross
> If you never heard of [cross](https://github.com/cross-rs/cross), an almost zero setup cross compilation cli setup

While there is no way to directly know if you are calling this cargo subcommand from cross, there is a `--target-command` flag which can be set to `cross` which will forward the feature flags to `cross` instead of `cargo`
</details>

## Why?

Expand Down Expand Up @@ -69,8 +141,8 @@ allowlist = ["foo", "bar"]

Licensed under either of

* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

at your option.

Expand Down
123 changes: 123 additions & 0 deletions src/bin/cargo-all-features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#![forbid(unsafe_code)]
#![deny(clippy::all)]

use std::process;

use cargo_all_features::{runner::CargoCommand, toolchain::CommandTarget, Options};
use clap::{crate_authors, crate_description, crate_version, Parser, Subcommand};
use yansi::Paint;

#[derive(Debug, Parser)]
#[clap(
name = env!("CARGO_BIN_NAME"),
author = crate_authors!(),
version = crate_version!(),
about = crate_description!(),
bin_name = "cargo all-features",
visible_alias = "all-features",
)]
struct Cli {
#[clap(long, help="The total number of chunks to split into. Only used for calculations", possible_values(["1.."]))]
pub chunks: Option<usize>,

#[clap(long, help="The chunk to process", possible_values(["1..<CHUNKS>"]))]
pub chunk: Option<usize>,

#[clap(long, help = "If enabled will not execute commands")]
pub dry_run: bool,

#[clap(long, help = "If enabled will not disable any coloring")]
pub no_color: bool,

#[clap(
long,
short,
help = "If enabled will show command which will or would be executed"
)]
pub verbose: bool,

#[clap(arg_enum)]
pub command: CargoCommand,

#[clap(arg_enum, long)]
pub command_target: Option<CommandTarget>,

#[clap(subcommand)]
pub flags_and_options: Option<FlagsAndOptions>,
}

#[derive(Debug, Subcommand)]
enum FlagsAndOptions {
#[clap(external_subcommand)]
External(Vec<String>),
}

// Runs the command and prints out in rust known error format
fn run_command(
command: CargoCommand,
args: &[String],
options: Option<Options>,
command_target: CommandTarget,
) {
if let Err(error) = cargo_all_features::run(command, args, options, command_target) {
println!("{}: {}", Paint::red("error").bold(), error);
}
}

// Main entrypoint for `cargo all-features`, cli as the frontend
pub fn main() {
// Name of the cargo subcommand
let name: String = env!("CARGO_BIN_NAME").replace("cargo-", "");

// Checking if command is used via cargo or as binary (such as using cargo build --bin all-features
let arguments = std::env::args().skip(
if std::env::args().nth(1).unwrap_or_else(|| "".to_string()) == name {
1
} else {
0
},
);

// Parsing input args
let args = Cli::parse_from(arguments);

// Checking if options are specified and transforming them into the libraries business logic
let mut options = Options {
no_color: args.no_color,
dry_run: args.dry_run,
verbose: args.verbose,
chunks: None,
chunk: None,
};

// Only if chunk and chunks are set
if args.chunks.is_some() && args.chunk.is_some() {
options.chunks = args.chunks;
options.chunk = args.chunk;
}

// Disable color
if args.no_color {
Paint::disable();
}

// Default to cargo
let command_target = args.command_target.unwrap_or(CommandTarget::Cargo);

// checking if cross is installed
if command_target == CommandTarget::Cross && which::which("cross").is_err() {
println!("{}: Could not find `cross` installed. To install it run `cargo install cross` or head over to https://github.com/cross-rs/cross for more information", Paint::red("error").bold());
process::exit(127);
}

// Either run with additional flags and subcommands or without
if let Some(external_command) = args.flags_and_options {
match external_command {
FlagsAndOptions::External(commands) => {
run_command(args.command, &commands, Some(options), command_target);
}
}
} else {
run_command(args.command, &[], Some(options), command_target);
}
}
11 changes: 7 additions & 4 deletions src/bin/cargo-build-all-features.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use cargo_all_features::{run, test_runner::CargoCommand};
use std::error::Error;
#![forbid(unsafe_code)]
#![deny(clippy::all)]

fn main() -> Result<(), Box<dyn Error>> {
run(CargoCommand::Build)
use cargo_all_features::runner::CargoCommand;
mod common;

fn main() {
common::deprecated_glue::run(CargoCommand::Build);
}
11 changes: 7 additions & 4 deletions src/bin/cargo-check-all-features.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use cargo_all_features::{run, test_runner::CargoCommand};
use std::error::Error;
#![forbid(unsafe_code)]
#![deny(clippy::all)]

fn main() -> Result<(), Box<dyn Error>> {
run(CargoCommand::Check)
use cargo_all_features::runner::CargoCommand;
mod common;

fn main() {
common::deprecated_glue::run(CargoCommand::Check);
}
10 changes: 6 additions & 4 deletions src/bin/cargo-test-all-features.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use cargo_all_features::{run, test_runner::CargoCommand};
use std::error::Error;
#![forbid(unsafe_code)]
#![deny(clippy::all)]

fn main() -> Result<(), Box<dyn Error>> {
run(CargoCommand::Test)
use cargo_all_features::runner::CargoCommand;
mod common;
fn main() {
common::deprecated_glue::run(CargoCommand::Test);
}
26 changes: 26 additions & 0 deletions src/bin/common/deprecated_glue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use cargo_all_features::{run as run_main, runner::CargoCommand, toolchain::CommandTarget};
use yansi::Paint;

// Glue code to run `cargo build-all-features`, etc. with same logic as `cargo all-features build`
pub fn run(command: CargoCommand) {
let name: String = env!("CARGO_BIN_NAME").replace("cargo-", "");
let arguments: Vec<String> = std::env::args()
.skip(
if std::env::args().nth(1).unwrap_or_else(|| "".to_string()) == name {
2
} else {
1
},
)
.collect();

println!(
"{}: the command `cargo {}` may be deprecated, please use `cargo all-features build`",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"{}: the command `cargo {}` may be deprecated, please use `cargo all-features build`",
"{}: the command `cargo {}` is deprecated, please use `cargo all-features build`",

Paint::yellow("warning").bold(),
name
);

if let Err(error) = run_main(command, &arguments, None, CommandTarget::Cargo) {
println!("{}: {}", Paint::red("error").bold(), error);
}
}
1 change: 1 addition & 0 deletions src/bin/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod deprecated_glue;
Loading