- CLI
- 存储
【免费下载链接】nvme-cli
NVMe management command line interface.
libnvme3 是 nvme-cli 仓库中面向 Python 的 SWIG 绑定包,本文以其发布说明(libnvme/libnvme3/PUBLISHING.md)为核心,结合 .github/workflows/libnvme-release-python.yml 与 pyproject.toml 等仓库实现,完整讲解 libnvme3 的发布机制、版本号生成逻辑、构建与验证流程,以及维护者如何通过 TestPyPI 测试发布、如何触发正式 PyPI 发布、如何安装验证产物。读完本文,你将掌握该包"零手工 twine 命令、全自动化"的发布模型,并能在本地复现构建与安装验证步骤。
一、发布对象:为什么只发 sdist 而不发 wheel
libnvme3 的 PyPI 发布只包含源码发行版(sdist),不产出二进制 wheel。这一点在 libnvme/libnvme3/PUBLISHING.md 中明确指出,原因可以从包的构建方式得到印证:
- pyproject.toml 使用
mesonpy作为构建后端,build-system.requires声明了meson-python、meson、ninja和swig四类构建依赖; - sdist 内包含 libnvme3 的完整源码树,安装时在目标机器上现场编译:先用 SWIG 从
.i接口文件生成 Python 包装代码,再经 Meson/Ninja 编译 C 库与绑定; [tool.meson-python.args]的setup段显式传入-Dnvme=disabled -Dlibnvme=enabled -Dpython=enabled -Dpypi=true等 Meson 选项,确保只构建库与 Python 绑定,而不构建 nvme-cli 命令行工具本身。
由此带来一个重要使用前提:最终用户在pip install libnvme3时,机器上必须预先具备 C 编译器、Meson、Ninja、SWIG 以及 libnvme 的 C 依赖,否则安装会失败。这也是 libnvme/libnvme3/README.md 中"优先从发行版软件仓库安装"(如 Debian/Ubuntu 的apt-get install python3-libnvme3、Fedora 的dnf install python3-libnvme3、openSUSE 的zypper install python3-libnvme3)的原因。
二、发布机制总览:GitHub Actions 全自动接管
发布流程完全由 GitHub Actions 工作流驱动,不需要任何人手工执行twine或pip发布命令。核心工作流是 .github/workflows/libnvme-release-python.yml,名为Release Python,其触发条件为:
- push 到
master分支(对应开发版发布通道); - push 任意 tag(对应正式版发布通道,且 tag 需满足版本格式校验);
- 支持
workflow_dispatch手动触发,可指定tag输入参数。
工作流内部划分为四个 job,形成两条发布链路:
| Job | 职责 | 所属链路 |
|---|---|---|
build_sdist | 构建正式版 sdist 并校验 | 正式版 → PyPI |
build_test_sdist | 基于git describe计算 dev 版本、构建开发版 sdist 并校验 | 开发版 → TestPyPI |
upload_test_pypi | 将开发版 sdist 发布到 TestPyPI | 开发版 → TestPyPI |
upload_pypi | 将正式版 sdist 发布到 PyPI | 正式版 → PyPI |
两条链路的依赖关系为:upload_test_pypi依赖build_test_sdist,upload_pypi依赖build_sdist。也就是说,每次 push 到 master 都会先构建开发版并发布到 TestPyPI;只有 tag 推送才会走正式版构建与 PyPI 发布。
三、开发版通道:每次 push 到 master 即发布到 TestPyPI
3.1 触发与条件
upload_test_pypijob 的触发条件包含两个关键约束(见 .github/workflows/libnvme-release-python.yml):
if: github.repository == 'linux-nvme/nvme-cli' && github.ref_type == 'branch'即只有主仓库的分支推送才会真正上传到 TestPyPI——fork 仓库中的 push 虽然会触发构建 job,但不会执行上传,这避免了 fork 污染测试索引。
3.2 dev 版本号如何生成
开发版的版本号并非固定值,而是由git describe动态推导。build_test_sdistjob 的关键逻辑(.github/workflows/libnvme-release-python.yml):
git describe --tags --abbrev=0取得最近一个 tag,例如v3.0;git rev-list HEAD --count取得自仓库起点以来的提交总数,例如123;- 去掉 tag 的
v前缀,拼接为${BASE_VERSION}.dev${REV},得到形如3.0.dev123的开发版本号; - 用
sed把该版本号写回meson.build的project(version: ...)字段,并提交一次ci: set project version to ...的版本号 bump commit。
注意该 job 使用fetch-depth: 0检出完整历史,这正是git describe计算版本号的前提。由于每次 push 的提交数不同,每个 dev 版本号都是唯一的、可追溯的,不会覆盖 TestPyPI 上已有的同名版本。
3.3 从 TestPyPI 安装验证
开发版发布后,维护者或其他开发者可安装验证,命令(来自 libnvme/libnvme3/PUBLISHING.md):
pip install \ --index-url https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ \ libnvme==<version>其中<version>需替换为实际推导出的开发版本号(如3.0.dev123)。同时指定两个索引的原因:libnvme3 的构建依赖(如 meson-python 等)大多只存在于正式 PyPI,因此--extra-index-url https://pypi.org/simple/保证依赖也能被解析到;而包本身优先从 TestPyPI 拉取,从而验证待发布内容。
四、正式版通道:推送版本 tag 即发布到 PyPI
4.1 触发条件与 tag 格式校验
正式版发布由upload_pypijob 承担(.github/workflows/libnvme-release-python.yml),其约束为:
if: startsWith(github.ref, 'refs/tags/v') && github.repository == 'linux-nvme/nvme-cli'此外,job 内还有一步更严格的 tag 正则校验:
if [[ "${{ github.ref }}" =~ ^refs/tags/v([0-9]+\.[0-9]+)(\.[0-9]+)?(rc[0-9]+)?$ ]]; then这意味着 tag 必须匹配vX.Y、vX.Y.Z,或带 RC 后缀的vX.YrcN/vX.Y.ZrcN形式,才会下载 sdist 产物并发布到 PyPI。不匹配的 tag(如vX.Y.Z-beta)会被静默跳过,不会产生发布。PUBLISHING.md 中提到的"tag 匹配vX.Y或vX.Y.Z"正是这条正则的文档化表述。
4.2 发布认证:OIDC,无需 API Token
两个上传 job 都声明了:
permissions: id-token: write并运行于environment: pypi。这表示发布动作使用OIDC(OpenID Connect)身份联合认证:GitHub Actions 以工作流身份直接换取 PyPI 的短期令牌,仓库中无需存储任何 PyPI API Token 作为发布凭据。这是 PUBLISHING.md 中"Publishing uses OIDC (id-token: write) — no API token is required"的实现依据。整个仓库中需要显式 Token 的仅有测试索引清理场景(见下文第六节)。
五、构建与校验环节:build+twine check双重把关
无论开发版还是正式版,sdist 在发布前都经过相同两道工序(.github/workflows/libnvme-release-python.yml):
pipx run build --sdist # 用 PyPA build 前端构建源码发行版 pipx run twine check dist/*.tar.gz # 校验 sdist 元数据与格式build --sdist是 PEP 517 标准构建前端,会读取 pyproject.toml 中声明的mesonpy后端并现场执行构建;twine check负责校验包元数据的规范性与 README 渲染等,只有通过校验的产物才会进入上传步骤;- 构建产物以 artifact 形式上载并保留 5 天(
retention-days: 5),供upload_*job 下载后发布。
两套构建分别运行在ghcr.io/linux-nvme/debian:latest(正式版)与ghcr.io/linux-nvme/debian.python:latest(开发版)容器镜像中,并使用固定的 Python 3.10 环境(PYTHON_VERSION: "3.10")。
六、配套的清理工作流:控制 TestPyPI 上的 dev 版本数量
由于每次 push 都会向 TestPyPI 发布一个新 dev 版本,长期积累会形成大量历史版本。仓库为此提供了配套工作流 .github/workflows/libnvme-cleanup-python.yml(Cleanup Python dev releases),用于按需清理:
- 仅支持
workflow_dispatch手动触发; - 两个输入参数:
keep-last(保留最近多少个 dev 版本,默认 5)与dry-run(默认true,只模拟不删除); - 实现上使用
pypi-cleanup工具,针对包名libnvme3、版本正则.*\.dev[0-9]+,对 TestPyPI 上超出保留数量的 dev 版本执行删除; - 与发布链路不同,该工作流需要
PYPI_API_TOKEN(存储在TEST_PYPI_API_TOKENsecret 中),因为它通过 API 执行删除操作,而非 OIDC 身份联合。
七、维护者操作速查
综合以上机制,面向 libnvme3 发布维护者的完整操作清单如下:
- 日常开发:直接 push 到
master。CI 会自动构建X.Y.devN版本并发布到 TestPyPI,无需任何手工操作; - 验证开发版:按第三节的命令从 TestPyPI 安装对应版本,验证绑定功能(可结合 libnvme/libnvme3/tests 下的测试脚本,如
test-objects.py、test-config.py做功能回归); - 发布正式版:推送符合
vX.Y/vX.Y.Z/vX.YrcN格式的 tag。CI 自动构建、校验并发布到 PyPI; - 清理测试索引:若 TestPyPI 上 dev 版本过多,手动触发 .github/workflows/libnvme-cleanup-python.yml,先以
dry-run=true预览,再关闭 dry-run 实际清理; - 最终用户安装:
pip install libnvme3(需预装 C 编译器、Meson、Ninja、SWIG 及 libnvme C 依赖);更推荐通过发行版包管理器安装python3-libnvme3。
八、小结
libnvme3 的发布体系是一个完全自动化的双通道模型:master 分支推送 → 动态 dev 版本 → TestPyPI,版本 tag 推送 → 正式版本 → PyPI,全程由 .github/workflows/libnvme-release-python.yml 驱动,构建统一走 PEP 517 的build --sdist+twine check,上传认证统一走 OIDC。维护者唯一需要掌握的"手工动作"是选择合适的 tag 格式与(可选地)运行清理工作流。理解这套机制后,无论是参与 nvme-cli 的 Python 绑定开发,还是为 libnvme3 打一个正式 release,都能准确预判 CI 行为与产物去向。
- CLI
- 存储
【免费下载链接】nvme-cli
NVMe management command line interface.
相关推荐
Tkinter-Designer 包发布实战:基于 Poetry 的 PyPI 与 TestPyPI 发布全流程指南
Tkinter Designer 包发布实战:基于 Poetry 的 PyPI 与 TestPyPI 发布全流程指南 导读 本文以 docs/instructi
开发工具代码生成低代码MindSpeed/Qwen3-1.7B-Base安全部署策略:企业级AI应用的安全最佳实践
MindSpeed/Qwen3 1.7B Base安全部署策略:企业级AI应用的安全最佳实践 在企业级AI应用部署中,确保MindSpeed/Qwen3 1.7
使用 Poetry 构建并发布 Tkinter-Designer CLI 到 PyPI:从 testpypi 验证到历史打包流程全解析
使用 Poetry 构建并发布 Tkinter Designer CLI 到 PyPI:从 testpypi 验证到历史打包流程全解析 本文基于 Tkinter
开发工具代码生成低代码
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考