diff --git a/bacon.toml b/bacon.toml index 22dcda8a..4706e6ec 100644 --- a/bacon.toml +++ b/bacon.toml @@ -16,15 +16,15 @@ command = ["cargo", "xtask", "check", "--all-features"] need_stdout = false [jobs.check-crossterm] -command = ["cargo", "xtask", "check-crossterm"] +command = ["cargo", "xtask", "check-backend", "crossterm"] need_stdout = false [jobs.check-termion] -command = ["cargo", "xtask", "check-termion"] +command = ["cargo", "xtask", "check-backend", "termion"] need_stdout = false [jobs.check-termwiz] -command = ["cargo", "xtask", "check-termwiz"] +command = ["cargo", "xtask", "check-backend", "termwiz"] need_stdout = false [jobs.clippy-all] @@ -52,7 +52,7 @@ need_stdout = false command = ["cargo", "xtask", "coverage"] [jobs.coverage-unit-tests-only] -command = ["cargo", "xtask", "coverage-unit"] +command = ["cargo", "xtask", "coverage", "--lib"] [jobs.hack] command = ["cargo", "xtask", "hack"] diff --git a/xtask/src/commands.rs b/xtask/src/commands.rs new file mode 100644 index 00000000..a377a636 --- /dev/null +++ b/xtask/src/commands.rs @@ -0,0 +1,177 @@ +use std::fmt::Debug; + +use backend::{Backend, TestBackend}; +use clap::Subcommand; +use color_eyre::Result; +use coverage::Coverage; +use duct::cmd; +use rdme::Readme; + +use self::{ + backend::CheckBackend, check::Check, clippy::Clippy, docs::Docs, format::Format, typos::Typos, +}; +use crate::{run_cargo, ExpressionExt, Run}; + +mod backend; +mod check; +mod clippy; +mod coverage; +mod docs; +mod format; +mod rdme; +mod typos; + +#[derive(Clone, Debug, Subcommand)] +pub enum Command { + /// Run CI checks (lint, build, test) + CI, + + /// Lint formatting, typos, clippy, and docs + #[command(visible_alias = "l")] + Lint, + + /// Build the project + #[command(visible_alias = "b")] + Build, + + #[command(visible_alias = "c")] + Check(Check), + + /// Run tests + #[command(visible_alias = "t")] + Test, + + /// Check backend + #[command(visible_alias = "cb")] + CheckBackend(CheckBackend), + + /// Check if README.md is up-to-date (using cargo-rdme) + #[command(visible_alias = "cr", alias = "rdme")] + Readme(Readme), + + /// Generate code coverage report + #[command(visible_alias = "cov")] + Coverage(Coverage), + + /// Run clippy on the project + #[command(visible_alias = "cl")] + Clippy(Clippy), + + /// Check documentation for errors and warnings + #[command(name = "docs", visible_alias = "d")] + Docs(Docs), + + /// Check for formatting issues in the project + #[command(visible_aliases = ["fmt", "f"])] + Format(Format), + + /// Lint markdown files + #[command(visible_alias = "md")] + LintMarkdown, + + /// Check for typos in the project + #[command(visible_alias = "ty")] + Typos(Typos), + + /// Test backend + #[command(visible_alias = "tb")] + TestBackend(TestBackend), + + /// Run doc tests + #[command(visible_alias = "td")] + TestDocs, + + /// Run lib tests + #[command(visible_alias = "tl")] + TestLibs, + + /// Run cargo hack to test each feature in isolation + #[command(visible_alias = "h")] + Hack, +} + +impl Run for Command { + fn run(self) -> crate::Result<()> { + match self { + Command::CI => ci(), + Command::Build => build(), + Command::Check(command) => command.run(), + Command::CheckBackend(command) => command.run(), + Command::Readme(command) => command.run(), + Command::Coverage(command) => command.run(), + Command::Lint => lint(), + Command::Clippy(command) => command.run(), + Command::Docs(command) => command.run(), + Command::Format(command) => command.run(), + Command::Typos(command) => command.run(), + Command::LintMarkdown => lint_markdown(), + Command::Test => test(), + Command::TestBackend(command) => command.run(), + Command::TestDocs => test_docs(), + Command::TestLibs => test_libs(), + Command::Hack => hack(), + } + } +} + +/// Run CI checks (lint, build, test) +fn ci() -> Result<()> { + lint()?; + build()?; + test()?; + Ok(()) +} + +/// Build the project +fn build() -> Result<()> { + run_cargo(vec!["build", "--all-targets", "--all-features"]) +} + +/// Lint formatting, typos, clippy, and docs (and a soft fail on markdown) +fn lint() -> Result<()> { + Clippy { fix: false }.run()?; + Docs { open: false }.run()?; + Format { check: true }.run()?; + Typos { fix: false }.run()?; + if let Err(err) = lint_markdown() { + tracing::warn!("known issue: markdownlint is currently noisy and can be ignored: {err}"); + } + Ok(()) +} + +/// Lint markdown files using [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) +fn lint_markdown() -> Result<()> { + cmd!("markdownlint-cli2", "**/*.md", "!target").run_with_trace()?; + Ok(()) +} + +/// Run tests for libs, backends, and docs +fn test() -> Result<()> { + test_libs()?; + for backend in [Backend::Crossterm, Backend::Termion, Backend::Termwiz] { + TestBackend { backend }.run()?; + } + test_docs()?; // run last because it's slow + Ok(()) +} + +/// Run doc tests for the workspace's default packages +fn test_docs() -> Result<()> { + run_cargo(vec!["test", "--doc", "--all-features"]) +} + +/// Run lib tests for the workspace's default packages +fn test_libs() -> Result<()> { + run_cargo(vec!["test", "--lib", "--all-targets", "--all-features"]) +} + +/// Run cargo hack to test each feature in isolation +fn hack() -> Result<()> { + run_cargo(vec![ + "hack", + "test", + "--lib", + "--each-feature", + "--workspace", + ]) +} diff --git a/xtask/src/commands/backend.rs b/xtask/src/commands/backend.rs new file mode 100644 index 00000000..245c9d81 --- /dev/null +++ b/xtask/src/commands/backend.rs @@ -0,0 +1,65 @@ +use clap::ValueEnum; +use color_eyre::Result; + +use crate::{run_cargo, Run}; + +/// Check backend +#[derive(Clone, Debug, clap::Args)] +pub struct CheckBackend { + /// Backend to check + pub backend: Backend, +} + +/// Test backend +#[derive(Clone, Debug, clap::Args)] +pub struct TestBackend { + /// Backend to test + pub backend: Backend, +} + +#[derive(Clone, Debug, ValueEnum, PartialEq, Eq)] +pub enum Backend { + Crossterm, + Termion, + Termwiz, +} + +impl Run for CheckBackend { + fn run(self) -> Result<()> { + if cfg!(windows) && self.backend == Backend::Termion { + tracing::error!("termion backend is not supported on Windows"); + } + let backend = match self.backend { + Backend::Crossterm => "crossterm", + Backend::Termion => "termion", + Backend::Termwiz => "termwiz", + }; + run_cargo(vec![ + "check", + "--all-targets", + "--no-default-features", + "--features", + backend, + ]) + } +} + +impl Run for TestBackend { + fn run(self) -> Result<()> { + if cfg!(windows) && self.backend == Backend::Termion { + tracing::error!("termion backend is not supported on Windows"); + } + let backend = match self.backend { + Backend::Crossterm => "crossterm", + Backend::Termion => "termion", + Backend::Termwiz => "termwiz", + }; + run_cargo(vec![ + "test", + "--all-targets", + "--no-default-features", + "--features", + backend, + ]) + } +} diff --git a/xtask/src/commands/check.rs b/xtask/src/commands/check.rs new file mode 100644 index 00000000..6283f2da --- /dev/null +++ b/xtask/src/commands/check.rs @@ -0,0 +1,21 @@ +use color_eyre::Result; + +use crate::{run_cargo, Run}; + +/// Run cargo check +#[derive(Clone, Debug, clap::Args)] +pub struct Check { + /// Check all features + #[arg(long, visible_alias = "all")] + all_features: bool, +} + +impl Run for Check { + fn run(self) -> Result<()> { + if self.all_features { + run_cargo(vec!["check", "--all-targets", "--all-features"]) + } else { + run_cargo(vec!["check", "--all-targets"]) + } + } +} diff --git a/xtask/src/commands/clippy.rs b/xtask/src/commands/clippy.rs new file mode 100644 index 00000000..8f6fe496 --- /dev/null +++ b/xtask/src/commands/clippy.rs @@ -0,0 +1,30 @@ +use color_eyre::Result; + +use crate::{run_cargo, Run}; + +/// Run clippy on the project +#[derive(Clone, Debug, clap::Args)] +pub struct Clippy { + /// Fix clippy warnings + #[arg(long)] + pub fix: bool, +} + +impl Run for Clippy { + fn run(self) -> Result<()> { + let mut args = vec![ + "clippy", + "--all-targets", + "--all-features", + "--tests", + "--benches", + "--", + "-D", + "warnings", + ]; + if self.fix { + args.push("--fix"); + } + run_cargo(args) + } +} diff --git a/xtask/src/commands/coverage.rs b/xtask/src/commands/coverage.rs new file mode 100644 index 00000000..58c57144 --- /dev/null +++ b/xtask/src/commands/coverage.rs @@ -0,0 +1,27 @@ +use color_eyre::Result; + +use crate::{run_cargo, Run}; + +/// Generate code coverage report +#[derive(Clone, Debug, clap::Args)] +pub struct Coverage { + /// Only generate coverage for unit tests + #[arg(long)] + pub lib: bool, +} + +impl Run for Coverage { + fn run(self) -> Result<()> { + let mut args = vec![ + "llvm-cov", + "--lcov", + "--output-path", + "target/lcov.info", + "--all-features", + ]; + if self.lib { + args.push("--lib"); + } + run_cargo(args) + } +} diff --git a/xtask/src/commands/docs.rs b/xtask/src/commands/docs.rs new file mode 100644 index 00000000..12c35865 --- /dev/null +++ b/xtask/src/commands/docs.rs @@ -0,0 +1,26 @@ +use color_eyre::Result; +use itertools::{Itertools, Position}; + +use crate::{run_cargo_nightly, workspace_libs, Run}; + +/// Check documentation for errors and warnings +#[derive(Clone, Debug, clap::Args)] +pub struct Docs { + /// Open the documentation in the browser + #[arg(long)] + pub open: bool, +} + +impl Run for Docs { + fn run(self) -> Result<()> { + let packages = workspace_libs()?; + for (position, package) in packages.iter().with_position() { + let mut args = vec!["docs-rs", "--package", &package]; + if self.open && matches!(position, Position::Last | Position::Only) { + args.push("--open"); + } + run_cargo_nightly(args)?; + } + Ok(()) + } +} diff --git a/xtask/src/commands/format.rs b/xtask/src/commands/format.rs new file mode 100644 index 00000000..bdafb01b --- /dev/null +++ b/xtask/src/commands/format.rs @@ -0,0 +1,40 @@ +use color_eyre::Result; +use duct::cmd; + +use crate::{run_cargo_nightly, ExpressionExt, Run}; + +/// Check for formatting issues in the project +#[derive(Clone, Debug, clap::Args)] +pub struct Format { + /// Check formatting issues + #[arg(long)] + pub check: bool, +} + +impl Run for Format { + fn run(self) -> Result<()> { + self.run_rustfmt()?; + self.run_taplo()?; + Ok(()) + } +} + +impl Format { + fn run_rustfmt(&self) -> Result<(), color_eyre::eyre::Error> { + let mut args = vec!["fmt", "--all"]; + if self.check { + args.push("--check"); + } + run_cargo_nightly(args)?; + Ok(()) + } + + fn run_taplo(&self) -> Result<(), color_eyre::eyre::Error> { + let mut args = vec!["format", "--colors", "always"]; + if self.check { + args.push("--check"); + } + cmd("taplo", args).run_with_trace()?; + Ok(()) + } +} diff --git a/xtask/src/commands/rdme.rs b/xtask/src/commands/rdme.rs new file mode 100644 index 00000000..de2d5f88 --- /dev/null +++ b/xtask/src/commands/rdme.rs @@ -0,0 +1,32 @@ +use color_eyre::Result; + +use crate::{run_cargo, workspace_libs, Run}; + +/// Check if README.md is up-to-date (using cargo-rdme) +#[derive(Clone, Debug, clap::Args)] +pub struct Readme { + /// Check if README.md is up-to-date + #[arg(long)] + check: bool, +} + +impl Run for Readme { + fn run(self) -> Result<()> { + let args = if self.check { + vec!["rdme", "--check"] + } else { + vec!["rdme"] + }; + for package in workspace_libs()? { + if package == "ratatui" { + // Skip the main crate as we removed rdme + continue; + } + let mut package_args = args.clone(); + package_args.push("--workspace-project"); + package_args.push(&package); + run_cargo(package_args)?; + } + Ok(()) + } +} diff --git a/xtask/src/commands/typos.rs b/xtask/src/commands/typos.rs new file mode 100644 index 00000000..99747964 --- /dev/null +++ b/xtask/src/commands/typos.rs @@ -0,0 +1,23 @@ +use color_eyre::Result; +use duct::cmd; + +use crate::{ExpressionExt, Run}; + +/// Check for typos in the project +#[derive(Clone, Debug, clap::Args)] +pub struct Typos { + /// Fix typos + #[arg(long)] + pub fix: bool, +} + +impl Run for Typos { + fn run(self) -> Result<()> { + if self.fix { + cmd!("typos", "--write-changes").run_with_trace()?; + } else { + cmd!("typos").run_with_trace()?; + } + Ok(()) + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 0440b7fa..813c3291 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -4,14 +4,20 @@ //! //! Run `cargo xtask --help` for more information -use std::{fmt::Debug, io, process::Output, vec}; +use std::{io, process::Output}; use cargo_metadata::{MetadataCommand, TargetKind}; -use clap::{Parser, Subcommand, ValueEnum}; +use clap::Parser; use clap_verbosity_flag::{InfoLevel, Verbosity}; use color_eyre::{eyre::Context, Result}; +use commands::Command; use duct::cmd; -use itertools::{Itertools, Position}; + +mod commands; + +pub trait Run { + fn run(self) -> Result<()>; +} fn main() -> Result<()> { color_eyre::install()?; @@ -21,7 +27,7 @@ fn main() -> Result<()> { .without_time() .init(); - match args.run() { + match args.command.run() { Ok(_) => (), Err(err) => { tracing::error!("{err}"); @@ -41,430 +47,20 @@ struct Args { verbosity: Verbosity, } -impl Args { - fn run(self) -> Result<()> { - self.command.run() - } -} - -#[derive(Clone, Debug, Subcommand)] -enum Command { - /// Run CI checks (lint, build, test) - CI, - - /// Build the project - #[command(visible_alias = "b")] - Build, - - #[command(visible_alias = "c")] - Check(CheckCommand), - - /// Run cargo check with crossterm feature - #[command(visible_alias = "cc")] - CheckCrossterm, - - /// Run cargo check with termion feature - #[command(visible_alias = "ct")] - CheckTermion, - - /// Run cargo check with termwiz feature - #[command(visible_alias = "cw")] - CheckTermwiz, - - /// Check if README.md is up-to-date (using cargo-rdme) - #[command(visible_alias = "cr", alias = "rdme")] - Readme(ReadmeCommand), - - /// Generate code coverage report - #[command(visible_alias = "cov")] - Coverage, - - /// Generate code coverage for unit tests only - #[command(visible_alias = "covu")] - CoverageUnit, - - /// Lint formatting, typos, clippy, and docs - #[command(visible_alias = "l")] - Lint, - - /// Run clippy on the project - #[command(visible_alias = "cl")] - Clippy(ClippyCommand), - - /// Check documentation for errors and warnings - #[command(name = "docs", visible_alias = "d")] - Docs(DocsCommand), - - /// Check for formatting issues in the project - #[command(visible_aliases = ["fmt", "f"])] - Format(FormatCommand), - - /// Lint markdown files - #[command(visible_alias = "md")] - LintMarkdown, - - /// Check for typos in the project - #[command(visible_alias = "ty")] - Typos(TyposCommand), - - /// Run tests - #[command(visible_alias = "t")] - Test, - - /// Test backend - #[command(visible_alias = "tb")] - TestBackend { backend: Backend }, - - /// Run doc tests - #[command(visible_alias = "td")] - TestDocs, - - /// Run lib tests - #[command(visible_alias = "tl")] - TestLibs, - - /// Run cargo hack to test each feature in isolation - #[command(visible_alias = "h")] - Hack, -} - -/// Run cargo check -#[derive(Clone, Debug, clap::Args)] -struct CheckCommand { - /// Check all features - #[arg(long, visible_alias = "all")] - all_features: bool, -} - -/// Check documentation for errors and warnings -#[derive(Clone, Debug, clap::Args)] -struct DocsCommand { - /// Open the documentation in the browser - #[arg(long)] - open: bool, -} - -/// Check for formatting issues in the project -#[derive(Clone, Debug, clap::Args)] -struct FormatCommand { - /// Check formatting issues - #[arg(long)] - check: bool, -} - -/// Run clippy on the project -#[derive(Clone, Debug, clap::Args)] -struct ClippyCommand { - /// Fix clippy warnings - #[arg(long)] - fix: bool, -} - -/// Check if README.md is up-to-date (using cargo-rdme) -#[derive(Clone, Debug, clap::Args)] -struct ReadmeCommand { - /// Check if README.md is up-to-date - #[arg(long)] - check: bool, -} - -/// Check for typos in the project -#[derive(Clone, Debug, clap::Args)] -struct TyposCommand { - /// Fix typos - #[arg(long)] - fix: bool, -} - -#[derive(Clone, Debug, ValueEnum, PartialEq, Eq)] -enum Backend { - Crossterm, - Termion, - Termwiz, -} - -impl Command { - fn run(self) -> Result<()> { - match self { - Command::CI => ci(), - Command::Build => build(), - Command::Check(command) => command.run(), - Command::CheckCrossterm => check_crossterm(), - Command::CheckTermion => check_termion(), - Command::CheckTermwiz => check_termwiz(), - Command::Readme(command) => command.run(), - Command::Coverage => coverage(), - Command::CoverageUnit => coverage_unit(), - Command::Lint => lint(), - Command::Clippy(command) => command.run(), - Command::Docs(command) => command.run(), - Command::Format(command) => command.run(), - Command::Typos(command) => command.run(), - Command::LintMarkdown => lint_markdown(), - Command::Test => test(), - Command::TestBackend { backend } => test_backend(backend), - Command::TestDocs => test_docs(), - Command::TestLibs => test_libs(), - Command::Hack => hack(), - } - } -} - -/// Run CI checks (lint, build, test) -fn ci() -> Result<()> { - lint()?; - build()?; - test()?; - Ok(()) -} - -/// Build the project -fn build() -> Result<()> { - run_cargo(vec!["build", "--all-targets", "--all-features"]) -} - -impl CheckCommand { - fn run(self) -> Result<()> { - if self.all_features { - run_cargo(vec!["check", "--all-targets", "--all-features"]) - } else { - run_cargo(vec!["check", "--all-targets"]) - } - } -} - -/// Run cargo check with crossterm feature -fn check_crossterm() -> Result<()> { - run_cargo(vec![ - "check", - "--all-targets", - "--all-features", - "--no-default-features", - "--features", - "crossterm", - ]) -} - -/// Run cargo check with termion feature -fn check_termion() -> Result<()> { - run_cargo(vec![ - "check", - "--all-targets", - "--all-features", - "--no-default-features", - "--features", - "termion", - ]) -} - -/// Run cargo check with termwiz feature -fn check_termwiz() -> Result<()> { - run_cargo(vec![ - "check", - "--all-targets", - "--all-features", - "--no-default-features", - "--features", - "termwiz", - ]) -} - -impl ReadmeCommand { - fn run(self) -> Result<()> { - let args = if self.check { - vec!["rdme", "--check"] - } else { - vec!["rdme"] - }; - for package in workspace_packages(TargetKind::Lib)? { - if package == "ratatui" { - // Skip the main crate as we removed rdme - continue; - } - let mut package_args = args.clone(); - package_args.push("--workspace-project"); - package_args.push(&package); - run_cargo(package_args)?; - } - Ok(()) - } -} - -/// Generate code coverage report -fn coverage() -> Result<()> { - run_cargo(vec![ - "llvm-cov", - "--lcov", - "--output-path", - "target/lcov.info", - "--all-features", - ]) -} - -/// Generate code coverage for unit tests only -fn coverage_unit() -> Result<()> { - run_cargo(vec![ - "llvm-cov", - "--lcov", - "--output-path", - "target/lcov-unit.info", - "--all-features", - "--lib", - ]) -} - -/// Lint formatting, typos, clippy, and docs (and a soft fail on markdown) -fn lint() -> Result<()> { - ClippyCommand { fix: false }.run()?; - DocsCommand { open: false }.run()?; - FormatCommand { check: true }.run()?; - TyposCommand { fix: false }.run()?; - if let Err(err) = lint_markdown() { - tracing::warn!("known issue: markdownlint is currently noisy and can be ignored: {err}"); - } - Ok(()) -} - -impl ClippyCommand { - fn run(self) -> Result<()> { - let mut args = vec![ - "clippy", - "--all-targets", - "--all-features", - "--tests", - "--benches", - "--", - "-D", - "warnings", - ]; - if self.fix { - args.push("--fix"); - } - run_cargo(args) - } -} - -impl DocsCommand { - fn run(self) -> Result<()> { - let packages = workspace_packages(TargetKind::Lib)?; - for (position, package) in packages.iter().with_position() { - let mut args = vec!["docs-rs", "--package", &package]; - if self.open && matches!(position, Position::Last | Position::Only) { - args.push("--open"); - } - run_cargo_nightly(args)?; - } - Ok(()) - } -} - -/// Return the available packages in the workspace -fn workspace_packages(kind: TargetKind) -> Result> { +/// Return the available libs in the workspace +fn workspace_libs() -> Result> { let meta = MetadataCommand::new() .exec() .wrap_err("failed to get cargo metadata")?; let packages = meta .workspace_packages() .iter() - .filter(|v| v.targets.iter().any(|t| t.kind.contains(&kind))) + .filter(|v| v.targets.iter().any(|t| t.kind.contains(&TargetKind::Lib))) .map(|v| v.name.clone()) .collect(); Ok(packages) } -impl FormatCommand { - fn run(self) -> Result<()> { - self.run_rustfmt()?; - self.run_taplo()?; - Ok(()) - } - - fn run_rustfmt(&self) -> Result<(), color_eyre::eyre::Error> { - let mut args = vec!["fmt", "--all"]; - if self.check { - args.push("--check"); - } - run_cargo_nightly(args)?; - Ok(()) - } - - fn run_taplo(&self) -> Result<(), color_eyre::eyre::Error> { - let mut args = vec!["format", "--colors", "always"]; - if self.check { - args.push("--check"); - } - cmd("taplo", args).run_with_trace()?; - Ok(()) - } -} - -/// Lint markdown files using [markdownlint-cli2](https://github.com/DavidAnson/markdownlint-cli2) -fn lint_markdown() -> Result<()> { - cmd!("markdownlint-cli2", "**/*.md", "!target").run_with_trace()?; - Ok(()) -} - -impl TyposCommand { - fn run(self) -> Result<()> { - if self.fix { - cmd!("typos", "--write-changes").run_with_trace()?; - } else { - cmd!("typos").run_with_trace()?; - } - Ok(()) - } -} - -/// Run tests for libs, backends, and docs -fn test() -> Result<()> { - test_libs()?; - test_backend(Backend::Crossterm)?; - test_backend(Backend::Termion)?; - test_backend(Backend::Termwiz)?; - test_docs()?; // run last because it's slow - Ok(()) -} - -/// Run tests for the specified backend -fn test_backend(backend: Backend) -> Result<()> { - if cfg!(windows) && backend == Backend::Termion { - tracing::error!("termion backend is not supported on Windows"); - } - let backend = match backend { - Backend::Crossterm => "crossterm", - Backend::Termion => "termion", - Backend::Termwiz => "termwiz", - }; - run_cargo(vec![ - "test", - "--all-targets", - "--no-default-features", - "--features", - backend, - ]) -} - -/// Run doc tests for the workspace's default packages -fn test_docs() -> Result<()> { - run_cargo(vec!["test", "--doc", "--all-features"]) -} - -/// Run lib tests for the workspace's default packages -fn test_libs() -> Result<()> { - run_cargo(vec!["test", "--lib", "--all-targets", "--all-features"]) -} - -/// Run cargo hack to test each feature in isolation -fn hack() -> Result<()> { - run_cargo(vec![ - "hack", - "test", - "--lib", - "--each-feature", - "--workspace", - ]) -} - /// Run a cargo subcommand with the default toolchain fn run_cargo(args: Vec<&str>) -> Result<()> { cmd("cargo", args).run_with_trace()?;