fd 发布工程全流程解析:从版本号升级到多平台产物发布的 Release Checklist
本文以 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 完成):
- 为本次发布所需的全部变更创建一个新分支;
- 更新 Cargo.toml 中的版本号,并运行
cargo build同步更新Cargo.lock,注意把Cargo.lock的变更一并git add; - 通过
grep rust-version Cargo.toml查出当前最低支持的 Rust 版本(MSRV); - 更新
README.md中fd的版本号与最低 Rust 版本说明; - 更新 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.lock并git add”仍需人工执行——Cargo.lock中fd-find包的版本条目(当前为version = "10.5.0")必须与Cargo.toml一致,否则后续 CI 的--locked构建会直接失败。
三、Pre-release checks and updates:发布前检查
3.1 清单原文的六项检查
- 安装最新版并验证:运行
cargo install --locked -f --path .,确认fd --version输出新版本号且在PATH中可用。--locked保证按Cargo.lock精确依赖构建,-f允许覆盖同名旧二进制; - 人工审阅帮助与手册:检查
-h、--help的输出以及 man 页(源文件为 doc/fd.1); - 同步 README 命令行选项:运行
fd -h并将其输出复制进 README.md 的 Command-line options 章节,可直接执行:gawk -i inplace -f scripts/update-help.awk README.md - 推送变更并等待 CI 通过(明确提示:CI 绿了才能进入下一阶段);
- 可选项:按 CHANGELOG.md 的说明手动测试新特性与新命令行选项;
- 干跑发布:运行
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=1、inSection=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.md,Command-line options 章节正是以 “This is the output of fd -h” 开头,随后跟着一段 Usage: fd [OPTIONS] ... 的帮助文本代码块——与 awk 脚本的生成/替换逻辑一一对应。
3.3 “等待 CI 成功”到底在等什么
.github/workflows/CICD.yml 定义了清单第 4 项所依赖的全部 CI 检查:
ensure_cargo_fmt:cargo fmt -- --check校验格式(仓库根目录有 rustfmt.toml 配置);lint_check:cargo clippy --all-targets --all-features -- -Dwarnings,警告即失败;min_version:安装crate_metadatajob 从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 清单原文的四步操作
- 合并发布分支:明确要求应为 fast-forward merge(发布分支基于 master 创建、期间 master 无新提交时);
- 打标签并推送:
git tag vX.Y.Z; git push origin tag vX.Y.Z,这会触发基于 tag 的部署流程;清单特别提醒——如果你的origin是 fork,要改推到upstream; - 在仓库的 Releases 页面创建 Release:选择新标签、以标签名作为标题,Release notes 直接复制 CHANGELOG.md 对应版本的小节,并可追加面向包维护者的补充说明,然后发布;
- 验证二进制部署产物:归档(archive)与 Debian 包应出现在 针对 Git tag 的那次 CI 运行 结束后生成的 Release 附件中;
- 发布到 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-release把PKG_PATH(如fd-v10.5.0-x86_64-unknown-linux-gnu.tar.gz)与DPKG_PATH上传为 Release 附件; - 另有一个
wingetjob:仅在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-musl且Conflicts: fd, fd-find;普通 glibc 包名fd,Conflicts: fd-musl, fd-find,避免与 musl 静态包互相覆盖; - 架构映射:
x86_64→amd64、i686→i386 系、aarch64→arm64、arm-hf→armhf,其余 target 直接报错终止(DPKG_ARCH=notset); - 包内容:二进制装入
usr/bin/fd,doc/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 的一次发布在仓库层面表现为:
release-X.Y.Z分支:scripts/version-bump.sh X.Y.Z改Cargo.toml/README.md/CHANGELOG.md,人工cargo build同步Cargo.lock并暂存;- 本地验证:
cargo install --locked -f --path .→fd --version、审阅fd --help与 doc/fd.1 →gawk -i inplace -f scripts/update-help.awk README.md→ 推送并等待 CICD 矩阵全绿 →cargo publish --dry-run; - 合并(fast-forward)→ 推送
vX.Y.Z标签 → 在 Releases 页面以 CHANGELOG 小节为 notes 创建 Release → 确认 tag 运行产出的 archives/deb 附件与 attestation; - 干净 clone 中
cargo publish上 crates.io; - 在 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 的骨架移植。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00