首页
/ fd 发布工程全流程解析:从版本号升级到多平台产物发布的 Release Checklist

fd 发布工程全流程解析:从版本号升级到多平台产物发布的 Release Checklist

2026-09-05 19:44:50作者:俞予舒Fleming

本文以 fd(A simple, fast and user-friendly alternative to find)仓库中的 发布检查清单 为主体,完整还原一个 Rust CLI 项目从“改版本号”到“发布多平台二进制产物与 crates.io 包”的标准化发布流程,并结合 版本升级脚本帮助文本更新脚本Debian 打包脚本CICD 工作流 的源码实现,说明每一步检查项背后的自动化支撑。读完后,你将掌握如何为 fd 这类项目执行一次完整、可复核的版本发布,以及如何设计自己的项目发布清单。

一、检查清单的定位:既可照做,也可粘贴进 PR

doc/release-checklist.md 开篇即说明它的两种用法:

This file can be used as-is, or copied into the GitHub PR description which includes necessary changes for the upcoming release.

即:既可以作为本地操作手册逐步勾选,也可以把整份清单复制进承载本次发布变更的 PR 描述中,让评审者对照逐项检查。整个清单分为四个阶段:Version bump(版本升级)→ Pre-release checks and updates(发布前检查)→ Release(正式发布)→ Post-release(发布后收尾)。下面按原文档的章节顺序逐一展开,并在每节补充仓库中可验证的脚本与 CI 实现。

二、Version bump:版本号升级的五项检查

2.1 清单原文的五项内容

原文档规定版本升级阶段包含以下检查项(可整体由 scripts/version-bump.sh 完成):

  1. 为本次发布所需的全部变更创建一个新分支;
  2. 更新 Cargo.toml 中的版本号,并运行 cargo build 同步更新 Cargo.lock,注意把 Cargo.lock 的变更一并 git add
  3. 通过 grep rust-version Cargo.toml 查出当前最低支持的 Rust 版本(MSRV);
  4. 更新 README.mdfd 的版本号与最低 Rust 版本说明;
  5. 更新 CHANGELOG.md:把 Upcoming release 章节的标题改为本次发布的版本号。

2.2 自动化实现:scripts/version-bump.sh

scripts/version-bump.sh 是清单 “Version bump” 一节的一键实现,脚本头部注释即写明 # This script automates the "Version bump" section。逐行拆解其逻辑:

version="$1"                    # 新版本号作为第一个参数传入,缺失则报错退出

git switch -C "release-$version"   # 创建并切换到 release-X.Y.Z 分支(对应检查项 1)
sed -i -e "0,/^\[badges/{s/^version =.*/version = \"$version\"/}" Cargo.toml
    # 仅替换 [badges] 段落之前(即 [package] 段内)的 version 行,
    # 避免误伤文件中其他位置的同名行
msrv="$(grep -F rust-version Cargo.toml | sed -e 's/^rust-version= "\(.*\)"/\1/')"
    # 从 Cargo.toml 提取 MSRV,对应检查项 3 的 grep 步骤
sed -i -e "s/Note that rust version \*[0-9.]+\* or later/Note that rust version *$msrv* or later/" README.md
    # 同步 README 中的最低 Rust 版本说明(对应检查项 4)
sed -i -e "s/^# Upcoming release/# $version/" CHANGELOG.md
    # 把 CHANGELOG 的 "# Upcoming release" 标题改为 "# X.Y.Z"(对应检查项 5)

两个实现细节值得注意:

  • Cargo.toml 的替换使用了 0,/^\[badges/ 限定“从文件头到 [badges] 段之间”的范围,确保只改 [package] 段的 version 字段。当前仓库 Cargo.toml 中该字段为 version = "10.5.0",同时声明了 rust-version = "1.90.0"edition = "2024"
  • 脚本只负责改版本号,并不执行 cargo build,所以清单中“运行 cargo build 更新 Cargo.lockgit add”仍需人工执行——Cargo.lockfd-find 包的版本条目(当前为 version = "10.5.0")必须与 Cargo.toml 一致,否则后续 CI 的 --locked 构建会直接失败。

三、Pre-release checks and updates:发布前检查

3.1 清单原文的六项检查

  1. 安装最新版并验证:运行 cargo install --locked -f --path .,确认 fd --version 输出新版本号且在 PATH 中可用。--locked 保证按 Cargo.lock 精确依赖构建,-f 允许覆盖同名旧二进制;
  2. 人工审阅帮助与手册:检查 -h--help 的输出以及 man 页(源文件为 doc/fd.1);
  3. 同步 README 命令行选项:运行 fd -h 并将其输出复制进 README.mdCommand-line options 章节,可直接执行:
    gawk -i inplace -f scripts/update-help.awk README.md
    
  4. 推送变更并等待 CI 通过(明确提示:CI 绿了才能进入下一阶段);
  5. 可选项:按 CHANGELOG.md 的说明手动测试新特性与新命令行选项;
  6. 干跑发布:运行 cargo publish --dry-run,提前暴露 crates.io 发布问题(文档在仓库中不完整、license 文件缺失等)。

3.2 update-help.awk:README 帮助块的自动再生

scripts/update-help.awk 是一个 gawk 脚本,核心状态机只有两个标志位:

  • /Command-line options/ 命中后 inSection=1,表示已定位到 README 的 ### Command-line options 章节;
  • 进入该章节后遇到第一个 ``` 围栏时 inBlock=1inSection=0;再遇到围栏时(即代码块结尾),执行 cargo run --release --quiet -- -h丢弃输出中 Usage 行之前的所有内容,把从 Usage: fd [OPTIONS] [pattern [path]...] 开始的真实帮助文本逐行写入,替换原代码块内容;
  • 任何命令执行失败都会向 stderr 打印 failed to generate help output 并以状态码 1 退出,避免生成残缺文档。

配合 gawk -i inplace(GNU awk 的原地修改模式),即可做到“README 中的选项列表永远与二进制实际输出一致”。对照当前 README.mdCommand-line options 章节正是以 “This is the output of fd -h” 开头,随后跟着一段 Usage: fd [OPTIONS] ... 的帮助文本代码块——与 awk 脚本的生成/替换逻辑一一对应。

3.3 “等待 CI 成功”到底在等什么

.github/workflows/CICD.yml 定义了清单第 4 项所依赖的全部 CI 检查:

  • ensure_cargo_fmtcargo fmt -- --check 校验格式(仓库根目录有 rustfmt.toml 配置);
  • lint_checkcargo clippy --all-targets --all-features -- -Dwarnings,警告即失败;
  • min_version:安装 crate_metadata job 从 Cargo.toml 提取的 MSRV 工具链(当前为 1.90.0),在最低 Rust 版本上运行 clippy 与 cargo test --locked,防止使用新版本才有的 API;
  • build:13 个 target 的构建矩阵(Linux gnu/musl 的 x86_64/i686/aarch64/arm、macOS 双架构、Windows msvc/gnu 双架构等),Linux 目标通过 cross 容器交叉编译,各 target 均执行 --locked --release 构建与测试。

发布分支推上去后,以上检查全部通过,才满足清单中 “wait for CI to succeed (before continuing with the next section)” 的前置条件。

四、Release:标签触发 + GitHub Release + crates.io

4.1 清单原文的四步操作

  1. 合并发布分支:明确要求应为 fast-forward merge(发布分支基于 master 创建、期间 master 无新提交时);
  2. 打标签并推送git tag vX.Y.Z; git push origin tag vX.Y.Z,这会触发基于 tag 的部署流程;清单特别提醒——如果你的 origin 是 fork,要改推到 upstream
  3. 在仓库的 Releases 页面创建 Release:选择新标签、以标签名作为标题,Release notes 直接复制 CHANGELOG.md 对应版本的小节,并可追加面向包维护者的补充说明,然后发布;
  4. 验证二进制部署产物:归档(archive)与 Debian 包应出现在 针对 Git tag 的那次 CI 运行 结束后生成的 Release 附件中;
  5. 发布到 crates.io:在干净的仓库中运行 cargo publish(例如重新 clone 一份再执行),保证打包内容不受本地工作区污染。

4.2 标签如何驱动产物发布

.github/workflows/CICD.yml 的触发条件包含 tags: '*',因此推送 vX.Y.Z 标签会再次运行整个矩阵。与 PR/分支运行相比,tag 运行的差异点在于:

  • 每个 build job 末尾的 Check for release 步骤用正则 ^refs/tags/v[0-9].* 判定本次是否为 tag 触发,是则置 IS_RELEASE=true
  • 仅当 IS_RELEASE=true 时,actions/attest 对 tarball 与 deb 产物做供应链溯源证明(attestation),随后 softprops/action-gh-releasePKG_PATH(如 fd-v10.5.0-x86_64-unknown-linux-gnu.tar.gz)与 DPKG_PATH 上传为 Release 附件;
  • 另有一个 winget job:仅在 refs/tags/v 前缀时运行,用 installers-regex: '-pc-windows-msvc\.zip$' 挑选 MSVC 版 Windows 压缩包提交到 Winget。

产物命名来自 build job 的 Create tarball 步骤:PKG_BASENAME=${name}-v${version}-${target}(Linux 为 .tar.gz、Windows 为 .zip),归档内容包含二进制、README、两份 LICENSE、CHANGELOG、man 页(doc/fd.1)与补全文件目录。Cargo.toml 中的 [package.metadata.binstall] 段(pkg-url = "{ repo }/releases/download/v{ version }/{ name}-v{ version }-{ target }.{ archive-format }")恰好描述了这套产物在 GitHub Release 中的固定 URL 结构,cargo binstall 正是按此模式下载发布包。

4.3 Debian 包:scripts/create-deb.sh 的产物细节

清单中“archives and Debian packages should appear” 的 deb 包由 CI 调用 scripts/create-deb.sh 生成(仅 Ubuntu runner 上的 Linux target)。脚本的关键行为:

  • musl 与普通变体区分:target 含 musl 时包名为 fd-muslConflicts: fd, fd-find;普通 glibc 包名 fdConflicts: fd-musl, fd-find,避免与 musl 静态包互相覆盖;
  • 架构映射x86_64→amd64i686→i386 系aarch64→arm64arm-hf→armhf,其余 target 直接报错终止(DPKG_ARCH=notset);
  • 包内容:二进制装入 usr/bin/fddoc/fd.1 压缩为 usr/share/man/man1/fd.1.gz,安装 bash/fish/zsh 三类补全,附带 README、双许可证与 gzip 压缩的 changelog;并创建 usr/bin/fdfind 符号链接及 fdfind 补全,保证 Debian 包用户可用 fdfind 别名(与 Cargo.toml 中包名 fd-find 保持一致,规避与发行版已有 fd 包冲突);
  • 版本号默认取 cargo metadata 输出的包版本,最终用 fakeroot dpkg-deb --build 构建;若检测到 GITHUB_OUTPUT,则把 DPKG_NAME/DPKG_PATH 写回 step outputs 供 CI 上传。

4.4 为什么 crates.io 要在“干净仓库”里发布

清单特意强调 clean repository,原因是 cargo publish 会把工作区文件打进 crate 包:本地构建产物(target/)、未提交改动、编辑器临时文件都会被裹入。重新 clone 一份再执行,等价于验证“发布物 == 标签源码”,与 tag 触发的 CI 构建形成交叉校验。第 3 节的 cargo publish --dry-run 则是把这类问题提前到合并前暴露。

五、Post-release:为下一个版本铺路

发布完成后,清单要求立刻在 CHANGELOG.md 顶部重建 Upcoming release 骨架,模板固定为四个小节:

# Upcoming release

## Features

## Bugfixes

## Changes

## Other

对照当前 CHANGELOG.md 的实际内容可以验证这一约定:文件以 # Upcoming Release 开头且 Features/Bugfixes 下仅有占位符 -,其下依次是 # 10.5.0# 10.4.2# 10.4.1… 各版本小节。值得注意的是,scripts/version-bump.sh 的 sed 规则 s/^# Upcoming release/# $version/ 与这里要求的首行标题存在大小写差异(release vs Release),实操中若沿用带大写 R 的标题,该行的版本化需要人工补一刀——这正是把清单放进 PR 描述、由评审者人工复核的价值所在。

六、流程总览:一次 fd 发布的完整链路

把四个阶段串起来,fd 的一次发布在仓库层面表现为:

  1. release-X.Y.Z 分支:scripts/version-bump.sh X.Y.ZCargo.toml/README.md/CHANGELOG.md,人工 cargo build 同步 Cargo.lock 并暂存;
  2. 本地验证:cargo install --locked -f --path .fd --version、审阅 fd --helpdoc/fd.1gawk -i inplace -f scripts/update-help.awk README.md → 推送并等待 CICD 矩阵全绿 → cargo publish --dry-run
  3. 合并(fast-forward)→ 推送 vX.Y.Z 标签 → 在 Releases 页面以 CHANGELOG 小节为 notes 创建 Release → 确认 tag 运行产出的 archives/deb 附件与 attestation;
  4. 干净 clone 中 cargo publish 上 crates.io;
  5. CHANGELOG.md 顶部重置 # Upcoming release 四段骨架,迎接下一个开发周期。

这套清单体现了 Rust CLI 项目的典型发布工程实践:版本元数据单点化(一切版本号以 Cargo.toml 为源,CI 的 crate_metadata job 直接从中提取 version/msrv)、文档即产物(README 帮助块由脚本从二进制输出再生,man 页随包分发)、发布即回归(tag 触发全矩阵 CI 重新验证后才挂载附件),以及清单即协作界面(复制进 PR 让每次发布可逐项复核)。对于其他 Rust CLI 项目,这四类做法都可以直接参照 doc/release-checklist.md 的骨架移植。

登录后查看全文
热门项目推荐
相关项目推荐