#xtask #extensible #cargo-workspace #cargo #cli #command-arguments

tracel-xtask

可复用和可扩展的 xtask 命令,用于管理仓库

3 个稳定版本

新版本 1.0.4 2024 年 8 月 28 日

#82Cargo 插件

MIT/Apache

115KB
2K SLoC

Tracel Xtask

Discord Current Crates.io Version Minimum Supported Rust Version Test Status license



一组易于使用和扩展的命令,用于您的基于 xtask CLI 的仓库,基于 clap

这些命令用于我们的 Tracel 仓库。通过集中所有冗余命令,我们可以节省大量的代码重复、模板代码,并大大降低维护负担。它还提供了一个跨所有仓库的统一接口。

这些命令不特定于 Tracel 仓库,并且它们应该可以在任何具有 cargo 工作区的 Rust 仓库以及其他不一定是 Rust 语言的仓库中使用。这些命令可以通过实用的 proc 宏以及遵循本 README 中描述的某些模式轻松扩展。

入门指南

使用 xtask 二进制crate设置 Cargo 工作区

  1. 创建一个新的 Cargo 工作区
cargo new my_workspace --vcs none
cd my_workspace
  1. 创建 xtask 二进制 crate
cargo new xtask --bin --vcs none
  1. 配置工作区

编辑工作区根目录下的 Cargo.toml 文件,包含以下内容

[workspace]
members = ["xtask"]
  1. 添加 tracel-xtask 依赖项

xtask/Cargo.toml 文件中,在 [dependencies] 下添加以下内容

[dependencies]
tracel-xtask = "1.0.0"
  1. 构建工作区
cargo build

您的工作区现在已设置,包含依赖于 tracel-xtask 版本 1.0.0 的 xtask 二进制 crate。

初始化 main.rs

  1. 在新建的 main.rs 文件中,导入 tracel_xtask prelude 并声明一个 Command 枚举,以及使用 macros::base_commands 属性从集合中选择您想要使用的基命令
use tracel_xtask::prelude::*;

#[macros::base_commands(
    Bump,
    Check,
    Fix
    Test,
)]
pub enum Command {}
  1. 更新 main 函数以初始化 xtask 解析器和调度基命令
fn main() -> anyhow::Result<()> {
    let args = init_xtask::<Command>()?;
    match args.command {
        // dispatch_base_commands function is generated by the commands macro
        _ => dispatch_base_commands(args),
    }
}
  1. 在仓库根目录下使用 cargo build 构建工作空间,以验证一切正常后再进入下一节。

  2. 现在您应该能够显示主帮助屏幕,其中列出了您之前选择的命令

cargo xtask --help

设置别名以方便调用

Cargo 别名

使用 cargo 调用 xtask 二进制文件非常冗长,实际上并不实用。幸运的是,我们可以创建一个 cargo 别名,使其轻松调用。

在您的仓库中创建一个新文件 .cargo/config.toml,内容如下

[alias]
xtask = "run --target-dir target/xtask --package xtask --bin xtask --"

这可以节省很多输入字符 :-) 您现在可以在仓库根目录下这样调用 xtask

cargo xtask

尝试使用 cargo xtask --help

Shell 别名

我们可以通过为 cargo xtask 创建 shell 别名来节省更多输入。

例如,我们可以将别名设置为 cx。以下是在各种 shell 中设置的示例。

  • 对于 bash
nano ~/.bashrc

# add this to the file
alias cx='cargo xtask'

# save and source the file or restart the shell session
source ~/.bashrc
  • 对于 zsh
nano ~/.zshrc

# add this to the file
alias cx='cargo xtask'

# save and source the file or restart the shell session
source ~/.zshrc
  • 对于 fish
nano ~/.config/fish/config.fish

# add this to the file
alias cx='cargo xtask'

# save and source the file or restart the shell session
source ~/.config/fish/config.fish
  • 对于 powershell
notepad $PROFILE

# add this at the end of file
function cx {
    cargo xtask $args
}

# save and quit then open a new powershell terminal

在仓库根目录下尝试使用 cx --help

约定

仓库结构

所有我们的仓库都遵循相同的目录层次结构

  • 一个 crates 目录,其中包含工作空间中的所有 crate
  • 一个 examples 目录,其中包含所有示例 crate
  • 一个 xtask 目录,这是一个使用 xtask-common 的 CLI 二进制 crate

集成测试

集成测试 通常包含在 tests 目录中。 tracel-xtask 测试命令期望所有集成文件名都以 test_ 为前缀。如果一个 crate 的 tests 目录中的文件不以 test_ 开头,则在运行 cargo xtask test integration 命令时将被忽略。

界面通用性

目标

tracel-xtask 命令行界面提供了 4 个默认目标

  • workspace,针对整个工作空间,这是默认目标
  • crates 包含所有二进制 crate 和库 crate
  • examples 包含所有示例 crate
  • all-packages 包含 cratesexamples 目标

workspaceall-packages 不同,因为 workspace 使用 cargo 的 --workspace 标志,而 all-packages 依赖于使用 --package 标志的 cratesexamples 目标。

以下是一些示例

# run all the crates tests
cargo xtask test --target crates all
# check format for examples, binaries and libs
cargo xtask check --target all-packages unit
# build the workspace
cargo xtask build --target workspace
# workspace is the default target so this has the same effect
cargo xtask build

全局选项

以下选项是全局的,必须在命令行上的实际命令之前使用,例如

  • 环境 (---environment)
cargo xtask -e production build

-e--environment 在基本命令中本身并不执行任何操作,它是一个标志,其唯一目的是通知您的自定义命令或调度函数目标环境,该环境可以是 development(默认)、stagingproduction

  • 执行环境(《-E`, `--execution-environment`)
cargo xtask -E no-std build

-E--execution-environment 在基本命令中本身并不执行任何操作,它是一个标志,其唯一目的是通知您的自定义命令或调度函数目标执行环境,该环境可以是 stdno-std

  • 覆盖率(《-c`, `--enable-coverage`)

-c--enabled-coverage,该操作将 Rust 工具链设置为生成覆盖率信息。

基本命令的结构

我们使用基于结构体、枚举和属性过程宏的 clap 的 derive API。每个基本命令是 base_commands 模块的子模块。如果命令有参数,则存在一个名为 <command>CmdArgs 的结构体,用于声明选项、参数和子命令。在子命令的情况下,定义了一个相应的枚举 <command>SubCommand

以下是一个 foo 命令的示例

#[macros::declare_command_args(Target, FooSubCommand)]
struct FooCmdArgs {}

pub enum FooSubCommand {
    /// A sub command for foo (usage on the command line: cargo xtask foo print-something)
    PrintSomething,
}

请注意,可以存在任意层次的嵌套子命令,但是更深层嵌套的子命令不能扩展,换句话说,只能扩展第一层的子命令。如果可能,尽量设计只有一级子命令的命令,以保持界面简单。

在以下章节中,我们将看到如何创建全新的命令以及如何扩展现有的基本命令。

自定义

创建新命令

  1. 首先,我们通过创建一个 commands 模块来组织命令。创建一个文件 xtask/src/commands/mycommand.rs 以及相应的 mod.rs 文件来声明模块内容。

  2. 然后,在 mycommand.rs 中,使用 declare_command_args 宏定义参数结构体和处理命令的函数。该 declare_command_args 接收两个参数,第一个是目标枚举的类型,第二个是子命令枚举的类型(如果有)。如果命令没有目标或没有子命令,则分别将每个参数设置为 None。`Target` 是由 tracel-xtask 提供的默认目标类型,如通用性部分所述。您可以从头创建类型,甚至可以像我们将在稍后章节中看到的那样扩展 Target

use tracel_xtask::prelude::*;

#[macros::declare_command_args(Target, None)]
struct MyCommandCmdArgs {}

pub fn handle_command(_args: MyCommandCmdArgs) -> anyhow::Result<()> {
    println!("Hello from my-command");
    Ok(())
}
  1. 确保更新 mod.rs 文件以声明命令模块
pub(crate) mod my_command;
  1. 现在,我们可以在 main.rs 中向 Command 枚举添加一个新变体
mod commands;

use tracel_xtask::prelude::*;

#[macros::base_commands(
    Bump,
    Check,
    Fix,
    Test,
)]
pub enum Command {
    MyCommand(commands::mycommand::MyCommandCmdArgs),
}
  1. 并将调度到我们的命令实现模块
fn main() -> anyhow::Result<()> {
    let args = init_xtask::<Command>()?;
    match args.command {
        Command::NewCommand(args) => commands::new_command::handle_command(args),
        _ => dispatch_base_commands(args),
    }
}
  1. 现在,您可以使用以下命令测试您的新命令
cargo xtask my-command --help

cargo xtask my-command

扩展默认的 Target 枚举

让我们实现一个新的命令 extended-target,以说明如何扩展默认的 Target 枚举。

  1. 创建 commands/extended_target.rs 文件并更新 mod.rs 文件,就像我们在上一节中看到的那样。

  2. 我们还需要将一个新的 strum 依赖项添加到我们的 Cargo.toml 文件中。

[dependencies]
strum = {version = "0.26.3", features = ["derive"]}
  1. 然后我们可以通过在 extended_target.rs 文件中扩展 Target 枚举的 macros::extend_targets 属性来扩展枚举。在这里,我们选择添加一个名为 frontend 的新目标,该目标针对一个我们可以在一个 monorepo 中找到的前端。
use tracel_xtask::prelude::*;

#[macros::extend_targets]
pub enum MyTarget {
    /// Target the frontend component of the monorepo.
    Frontend,
}
  1. 然后我们通过引用我们新创建的 MyTarget 枚举来定义我们的命令参数。
#[macros::declare_command_args(MyTarget, None)]
struct ExtendedTargetCmdArgs {}
  1. 我们的新目标随后可用于在 handle_command 函数中使用。
pub fn handle_command(args: ExtendedTargetCmdArgs) -> anyhow::Result<()> {
    match args.target {
        // Default targets
        MyTarget::AllPackages => println!("You chose the target: all-packages"),
        MyTarget::Crates => println!("You chose the target: crates"),
        MyTarget::Examples => println!("You chose the target: examples"),
        MyTarget::Workspace => println!("You chose the target: workspace"),

        // Additional target
        MyTarget::Frontend => println!("You chose the target: frontend"),
    };
    Ok(())
}
  1. 通过将新命令添加到我们的 Command 枚举并在 main 函数中分发它来注册我们的新命令的常规方式。
mod commands;

use tracel_xtask::prelude::*;

#[macros::base_commands(
    Bump,
    Check,
    Fix,
    Test,
)]
pub enum Command {
    ExtendedTarget(commands::extended_target::ExtendedTargetCmdArgs),
}

fn main() -> anyhow::Result<()> {
    let args = init_xtask::<Command>()?;
    match args.command {
        Command::ExtendedTarget(args) => commands::extended_target::handle_command(args),
        _ => dispatch_base_commands(args),
    }
}
  1. 用以下方式测试命令:
cargo xtask extended-target --help

cargo xtask extended-target --target frontend

扩展基本命令

要扩展现有命令,我们使用 macros::extend_command_args 属性,该属性接受三个参数:

  • 第一个参数是要扩展的基本命令参数结构的类型,
  • 第二个参数是目标类型(如果没有目标,则为 None),
  • 第三个参数是子命令类型(如果没有子命令,则为 None)。

让我们用两个例子来说明这一点,第一个是将新 --debug 参数扩展到 build 基本命令的命令,第二个是扩展 check 基本命令的子命令,添加一个新的 my-check 子命令的新命令。

您可以在本存储库的 xtask crate 中找到更多示例。

扩展基本命令的参数

我们创建了一个名为 extended-build-args 的新命令,它有一个额外的参数 --debug

  1. 创建 commands/extended_build_args.rs 文件并更新 mod.rs 文件,就像我们在上一节中看到的那样。

  2. 使用宏 macros::extend_command_args 扩展 BuildCommandArgs 结构,并定义 handle_command 函数。请注意,宏自动实现了 TryInto 特性,这使得它可以轻松地分发回基础命令自己的 handle_command 函数。还请注意,如果基础命令需要目标,那么您也需要提供目标,即如果基础命令具有 Target,则宏的目标参数不能为 None

use tracel_xtask::prelude::*;

#[macros::extend_command_args(BuildCmdArgs, Target, None)]
pub struct ExtendedBuildArgsCmdArgs {
    /// Print additional debug info when set
    #[arg(short, long)]
    pub debug: bool,
}

pub fn handle_command(args: ExtendedBuildArgsCmdArgs) -> anyhow::Result<()> {
    if args.debug {
        println!("Debug is enabled");
    }
    base_commands::build::handle_command(args.try_into().unwrap())
}
  1. 通过将新命令添加到我们的 Command 枚举并在 main 函数中分发它来注册新命令的常规方式。
mod commands;

use tracel_xtask::prelude::*;

#[macros::base_commands(
    Bump,
    Check,
    Fix,
    Test,
)]
pub enum Command {
    ExtendedBuildArgs(commands::extended_build_args::ExtendedBuildArgsCmdArgs),
}

fn main() -> anyhow::Result<()> {
    let args = init_xtask::<Command>()?;
    match args.command {
        Command::ExtendedBuildArgs(args) => commands::extended_build_args::handle_command(args),
        _ => dispatch_base_commands(args),
    }
}
  1. 用以下方式测试命令:
cargo xtask extended-build-args --help

cargo xtask extended-build-args --debug

扩展基本命令的子命令

对于这个例子,我们创建了一个名为 extended-check-subcommands 的新命令。

  1. 创建 commands/extended_check_subcommands.rs 文件并更新 mod.rs 文件,就像我们在上一节中看到的那样。

  2. 使用宏 macros::extend_command_args 扩展 CheckCommandArgs 结构。

use tracel_xtask::prelude::*;

#[macros::extend_command_args(CheckCmdArgs, Target, ExtendedCheckSubcommand)]
pub struct ExtendedCheckedArgsCmdArgs {}
  1. 通过使用宏 extend_subcommandsExtendedCheckSubcommand 枚举实现为通过扩展 CheckSubcommand 基础枚举。它接受要扩展的子命令枚举类型的名称。
#[macros::extend_subcommands(CheckSubCommand)]
pub enum ExtendedCheckSubcommand {
    /// An additional subcommand for our extended check command.
    MySubcommand,
}
  1. 实现 handle_command 函数以处理新的子命令。请注意,我们还需要处理 All 变体。
use strum::IntoEnumIterator;

pub fn handle_command(args: ExtendedCheckedArgsCmdArgs) -> anyhow::Result<()> {
    match args.get_command() {
        ExtendedCheckSubcommand::MySubcommand => run_my_subcommand(args.clone()),
        ExtendedCheckSubcommand::All => {
            ExtendedCheckSubcommand::iter()
                .filter(|c| *c != ExtendedCheckSubcommand::All)
                .try_for_each(|c| {
                    handle_command(
                        ExtendedCheckedArgsCmdArgs {
                            command: Some(c),
                            target: args.target.clone(),
                            exclude: args.exclude.clone(),
                            only: args.only.clone(),
                        },
                    )
                })
        }
        _ => base_commands::check::handle_command(args.try_into().unwrap()),
    }
}

fn run_my_subcommand(_args: ExtendedCheckedArgsCmdArgs) -> Result<(), anyhow::Error> {
    println!("Executing new subcommand");
    Ok(())
}
  1. 通过将新命令添加到我们的 Command 枚举并在 main 函数中分发它来注册新命令的常规方式。
mod commands;

use tracel_xtask::prelude::*;

#[macros::base_commands(
    Bump,
    Check,
    Fix,
    Test,
)]
pub enum Command {
    ExtendedCheckSubcommand(commands::extended_check_subcommands::ExtendedCheckedArgsCmdArgs),
}

fn main() -> anyhow::Result<()> {
    let args = init_xtask::<Command>()?;
    match args.command {
        Command::ExtendedCheckSubcommand(args) => commands::extended_check_subcommands::handle_command(args),
        _ => dispatch_base_commands(args),
    }
}
  1. 用以下方式测试命令:
cargo xtask extended-check-subcommands --help

cargo xtask extended-check-subcommands my-check

自定义构建和测试

xtask-common 提供了辅助函数,可以轻松执行带有特定功能或构建目标的自定义构建或测试(不要混淆 Rust 构建目标,它是我们在此处介绍的 xtask 目标的参数)。

例如,我们可以使用 build 命令的 Extend an existing xtask-common command 部分,并使用辅助函数构建带有自定义功能或构建目标的额外 crate。

pub fn handle_command(mut args: tracel_xtask::commands::build::BuildCmdArgs)  -> anyhow::Result<()> {
    // regular execution of the build command
    tracel_xtask::commands::build::handle_command(args)?;

    // additional crate builds
    // build 'my-crate' with all the features
    tracel_xtask::utils::helpers::custom_crates_build(vec!["my-crate"], vec!["--all-features"], None, None, "all features")?;
    // build 'my-crate' with specific features
    tracel_xtask::utils::helpers::custom_crates_build(vec!["my-crate"], vec!["--features", "myfeature1,myfeature2"], None, None, "myfeature1,myfeature2")?;
    // build 'my-crate' with a different target than the default one
    tracel_xtask::utils::helpers::custom_crates_build(vec!["my-crate"], vec!["--target", "thumbv7m-none-eabi"], None, None, "thumbv7m-none-eabi target")?;
    Ok(())
}

启用并生成覆盖率信息

以下是一个 GitHub 作业示例,展示了如何设置覆盖率、启用它并将覆盖率信息上传到 codecov。

env:
  GRCOV_LINK: "https://github.com/mozilla/grcov/releases/download"
  GRCOV_VERSION: "0.8.19"

jobs:
  my-job:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: install rust
        uses: dtolnay/rust-toolchain@master
        with:
          components: rustfmt, clippy
          toolchain: stable
      - name: Install grcov
        shell: bash
        run: |
          curl -L "$GRCOV_LINK/v$GRCOV_VERSION/grcov-x86_64-unknown-linux-musl.tar.bz2" |
          tar xj -C $HOME/.cargo/bin
          cargo xtask coverage install
      - name: Build
        shell: bash
        run: cargo xtask build
      - name: Tests
        shell: bash
        run: cargo xtask --enable-coverage test all
      - name: Generate lcov.info
        shell: bash
        # /* is to exclude std library code coverage from analysis
        run: cargo xtask coverage generate --ignore "/*,xtask/*,examples/*"
      - name: Codecov upload lcov.info
        uses: codecov/codecov-action@v4
        with:
          files: lcov.info
          token: ${{ secrets.CODECOV_TOKEN }}

特殊命令 'validate'

按照惯例,此命令负责运行所有检查、构建和/或测试,以验证在打开拉取请求或合并请求之前代码。

可以通过宏 tracel_xtask_macros::commands 添加 Validate 命令,就像其他命令一样。

默认情况下,所有来自 check 命令的检查都会运行,以及来自 test 命令的单元测试和集成测试。

如果您需要执行更多验证,可以创建自己的 handle_command 函数。理想情况下,此函数应仅调用其他命令的 handle_command 函数。

以下是执行所有检查并对工作区执行测试的简单示例。

pub fn handle_command(args: ValidateCmdArgs) -> anyhow::Result<()> {
    let target = Target::Workspace;
    let exclude = vec![];
    let only = vec![];

    // checks
    [
        CheckSubCommand::Audit,
        CheckSubCommand::Format,
        CheckSubCommand::Lint,
        CheckSubCommand::Typos,
    ]
    .iter()
    .try_for_each(|c| {
        super::check::handle_command(CheckCmdArgs {
            target: target.clone(),
            exclude: exclude.clone(),
            only: only.clone(),
            command: Some(c.clone()),
            ignore_audit: args.ignore_audit,
        })
    })?;

    // tests
    super::test::handle_command(TestCmdArgs {
        target: target.clone(),
        exclude: exclude.clone(),
        only: only.clone(),
        threads: None,
        jobs: None,
        command: Some(TestSubCommand::All),
    })?;

    Ok(())
}

基本命令列表

检查和修复

checkfix 包含相同的子命令,用于审计、格式化、lint 或校对代码库。

虽然 check 命令仅报告问题,但 fix 命令会尝试在遇到时修复它们。

checkfix 命令旨在帮助您在开发过程中维护代码质量。它们运行各种检查并修复问题,确保您的代码干净并遵循最佳实践。

每个测试都可以单独执行,也可以使用 all 顺序执行所有测试。

用于对代码库进行 lint 的用法

cargo xtask check lint
cargo xtask fix lint
cargo xtask fix all

运行测试

测试是开发的关键部分,test 命令旨在使此过程变得简单。

此命令区分单元测试和集成测试。 单元测试 是在 crate 的 src 目录下的内联测试。 集成测试 是定义在 crate 的 tests 目录下的文件的测试,除了 src 目录。

用法

# execute workspace unit tests
cargo xtask test unit
# execute workspace integration tests
cargo xtask test integration
# execute workspace both unit tests and integration tests
cargo xtask test all

请注意,文档测试支持位于 doc 命令下。

文档

构建和测试工作区文档的命令。

增加版本号

这是一个为仓库维护者保留的命令。

使用 bump 命令更新仓库中所有 crate 的版本号。这在您准备新版本时尤其有用,需要确保所有 crate 都有正确的版本。

您可以通过主要、次要或补丁级别增加版本,具体取决于所做的更改。例如,如果您已进行了破坏性更改,应增加主要版本。对于向后兼容的新功能,增加次要版本。对于错误修复,增加补丁版本。

用法

cargo xtask bump <COMMAND>

发布 crate

这是一个为仓库维护者保留的命令,主要在 publish GitHub 工作流中使用。

该命令自动将软件包发布到 crates.io,Rust 软件包注册处。通过指定软件包名称,xtask 处理发布过程,确保软件包可供他人使用。

用法

cargo xtask publish <NAME>

通常,该命令用于调用 Tracel 的可重用 publish-crate 工作流的 GitHub 工作流。以下是一个简单示例,其中工作流发布两个软件包 A 和 B,其中 A 依赖于 B。

name: publish all crates

on:
  push:
    tags:
      - "v*"

jobs:
  publish-B:
    uses: tracel-ai/github-actions/.github/workflows/publish-crate.yml@v1
    with:
      crate: B
    secrets:
      CRATES_IO_API_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}

  # --------------------------------------------------------------------------------
  publish-A:
    uses: tracel-ai/github-actions/.github/workflows/publish-crate.yml@v1
    with:
      crate: A
    needs:
      - publish-B
    secrets:
      CRATES_IO_API_TOKEN: ${{ secrets.CRATES_IO_API_TOKEN }}

覆盖率

此命令提供子命令以安装执行代码覆盖率所需的依赖项,以及子命令以生成可以上传到 codecov 等处的覆盖率信息文件。请参阅专用部分 启用并生成覆盖率信息

依赖项

有关依赖项的多个附加命令。

deny 确保cargo-deny使用所有依赖项满足要求。

unused 检测工作空间中未使用的依赖项。

漏洞

该命令使执行如 Rust 不稳定书籍 中所述的清理器变得更加容易。

这些清理器需要夜间工具链。

Run the specified vulnerability check locally. These commands must be called with 'cargo +nightly'

Usage: xtask vulnerabilities <COMMAND>

Commands:
  all                            Run all most useful vulnerability checks
  address-sanitizer              Run Address sanitizer (memory error detector)
  control-flow-integrity         Run LLVM Control Flow Integrity (CFI) (provides forward-edge control flow protection)
  hw-address-sanitizer           Run newer variant of Address sanitizer (memory error detector similar to AddressSanitizer, but based on partial hardware assistance)
  kernel-control-flow-integrity  Run Kernel LLVM Control Flow Integrity (KCFI) (provides forward-edge control flow protection for operating systems kerneljs)
  leak-sanitizer                 Run Leak sanitizer (run-time memory leak detector)
  memory-sanitizer               Run memory sanitizer (detector of uninitialized reads)
  mem-tag-sanitizer              Run another address sanitizer (like AddressSanitizer and HardwareAddressSanitizer but with lower overhead suitable for use as hardening for production binaries)
  nightly-checks                 Run nightly-only checks through cargo-careful `<https://crates.io/crates/cargo-careful>`
  safe-stack                     Run SafeStack check (provides backward-edge control flow protection by separating stack into safe and unsafe regions)
  shadow-call-stack              Run ShadowCall check (provides backward-edge control flow protection - aarch64 only)
  thread-sanitizer               Run Thread sanitizer (data race detector)
  help                           Print this message or the help of the given subcommand(s)

依赖项

~6.5–9MB
~152K SLoC