3 个稳定版本
新版本 1.0.4 | 2024 年 8 月 28 日 |
---|
#82 在 Cargo 插件
115KB
2K SLoC
一组易于使用和扩展的命令,用于您的基于 xtask CLI 的仓库,基于 clap。
这些命令用于我们的 Tracel 仓库。通过集中所有冗余命令,我们可以节省大量的代码重复、模板代码,并大大降低维护负担。它还提供了一个跨所有仓库的统一接口。
这些命令不特定于 Tracel 仓库,并且它们应该可以在任何具有 cargo 工作区的 Rust 仓库以及其他不一定是 Rust 语言的仓库中使用。这些命令可以通过实用的 proc 宏以及遵循本 README 中描述的某些模式轻松扩展。
入门指南
使用 xtask 二进制crate设置 Cargo 工作区
- 创建一个新的 Cargo 工作区
cargo new my_workspace --vcs none
cd my_workspace
- 创建
xtask
二进制 crate
cargo new xtask --bin --vcs none
- 配置工作区
编辑工作区根目录下的 Cargo.toml
文件,包含以下内容
[workspace]
members = ["xtask"]
- 添加
tracel-xtask
依赖项
在 xtask/Cargo.toml
文件中,在 [dependencies]
下添加以下内容
[dependencies]
tracel-xtask = "1.0.0"
- 构建工作区
cargo build
您的工作区现在已设置,包含依赖于 tracel-xtask
版本 1.0.0 的 xtask
二进制 crate。
初始化 main.rs
- 在新建的
main.rs
文件中,导入tracel_xtask prelude
并声明一个Command
枚举,以及使用macros::base_commands
属性从集合中选择您想要使用的基命令
use tracel_xtask::prelude::*;
#[macros::base_commands(
Bump,
Check,
Fix
Test,
)]
pub enum Command {}
- 更新
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),
}
}
-
在仓库根目录下使用
cargo build
构建工作空间,以验证一切正常后再进入下一节。 -
现在您应该能够显示主帮助屏幕,其中列出了您之前选择的命令
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 和库 crateexamples
包含所有示例 crateall-packages
包含crates
和examples
目标
workspace
和 all-packages
不同,因为 workspace
使用 cargo 的 --workspace
标志,而 all-packages
依赖于使用 --package
标志的 crates
和 examples
目标。
以下是一些示例
# 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
(默认)、staging
或 production
。
- 执行环境(《-E`, `--execution-environment`)
cargo xtask -E no-std build
-E
或 --execution-environment
在基本命令中本身并不执行任何操作,它是一个标志,其唯一目的是通知您的自定义命令或调度函数目标执行环境,该环境可以是 std
或 no-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,
}
请注意,可以存在任意层次的嵌套子命令,但是更深层嵌套的子命令不能扩展,换句话说,只能扩展第一层的子命令。如果可能,尽量设计只有一级子命令的命令,以保持界面简单。
在以下章节中,我们将看到如何创建全新的命令以及如何扩展现有的基本命令。
自定义
创建新命令
-
首先,我们通过创建一个
commands
模块来组织命令。创建一个文件xtask/src/commands/mycommand.rs
以及相应的mod.rs
文件来声明模块内容。 -
然后,在
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(())
}
- 确保更新
mod.rs
文件以声明命令模块
pub(crate) mod my_command;
- 现在,我们可以在
main.rs
中向Command
枚举添加一个新变体
mod commands;
use tracel_xtask::prelude::*;
#[macros::base_commands(
Bump,
Check,
Fix,
Test,
)]
pub enum Command {
MyCommand(commands::mycommand::MyCommandCmdArgs),
}
- 并将调度到我们的命令实现模块
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),
}
}
- 现在,您可以使用以下命令测试您的新命令
cargo xtask my-command --help
cargo xtask my-command
扩展默认的 Target 枚举
让我们实现一个新的命令 extended-target
,以说明如何扩展默认的 Target 枚举。
-
创建
commands/extended_target.rs
文件并更新mod.rs
文件,就像我们在上一节中看到的那样。 -
我们还需要将一个新的
strum
依赖项添加到我们的Cargo.toml
文件中。
[dependencies]
strum = {version = "0.26.3", features = ["derive"]}
- 然后我们可以通过在
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,
}
- 然后我们通过引用我们新创建的
MyTarget
枚举来定义我们的命令参数。
#[macros::declare_command_args(MyTarget, None)]
struct ExtendedTargetCmdArgs {}
- 我们的新目标随后可用于在
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(())
}
- 通过将新命令添加到我们的
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),
}
}
- 用以下方式测试命令:
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
。
-
创建
commands/extended_build_args.rs
文件并更新mod.rs
文件,就像我们在上一节中看到的那样。 -
使用宏
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())
}
- 通过将新命令添加到我们的
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),
}
}
- 用以下方式测试命令:
cargo xtask extended-build-args --help
cargo xtask extended-build-args --debug
扩展基本命令的子命令
对于这个例子,我们创建了一个名为 extended-check-subcommands
的新命令。
-
创建
commands/extended_check_subcommands.rs
文件并更新mod.rs
文件,就像我们在上一节中看到的那样。 -
使用宏
macros::extend_command_args
扩展CheckCommandArgs
结构。
use tracel_xtask::prelude::*;
#[macros::extend_command_args(CheckCmdArgs, Target, ExtendedCheckSubcommand)]
pub struct ExtendedCheckedArgsCmdArgs {}
- 通过使用宏
extend_subcommands
将ExtendedCheckSubcommand
枚举实现为通过扩展CheckSubcommand
基础枚举。它接受要扩展的子命令枚举类型的名称。
#[macros::extend_subcommands(CheckSubCommand)]
pub enum ExtendedCheckSubcommand {
/// An additional subcommand for our extended check command.
MySubcommand,
}
- 实现
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(())
}
- 通过将新命令添加到我们的
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),
}
}
- 用以下方式测试命令:
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(())
}
基本命令列表
检查和修复
check
和 fix
包含相同的子命令,用于审计、格式化、lint 或校对代码库。
虽然 check
命令仅报告问题,但 fix
命令会尝试在遇到时修复它们。
check
和 fix
命令旨在帮助您在开发过程中维护代码质量。它们运行各种检查并修复问题,确保您的代码干净并遵循最佳实践。
每个测试都可以单独执行,也可以使用 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