Anki 版本发布全流程指南:从 prepare 脚本到 GitHub Actions 的自动化发布管线
2026/9/10 7:57:05 网站建设 项目流程

Anki 版本发布全流程指南:从 prepare 脚本到 GitHub Actions 的自动化发布管线

【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki

导读

Anki(间隔重复记忆卡片程序)的版本发布并不是手工打 tag、上传安装包的简单操作,而是一条**「本地脚本准备 + CI 自动验证 + GitHub Actions 跨平台构建发布」**的自动化流水线。本文以仓库中的官方发布文档 docs/releasing.md 为主体,结合 tools/prepare_release.py、.github/workflows/release.yml、release.just 等源码,完整讲解 Anki 的版本号规范、release/YY.MM分支工作流、发布参数矩阵、环境审批门禁、测试发布方法,以及独立的anki-audio音频包发布流程。读完后,你将能理解并复现 Anki 从开发分支到 PyPI/GitHub Release 的完整发布链路。

发布流水线总览

Anki 的版本发布由两部分组成:

  1. tools/prepare_release.py(本地执行)—— 由维护者在自己的机器上运行,负责校验版本号、切换对应 release 分支、检查 CI 是否通过、确认没有重复的 tag 或 release、同步翻译文件,最后提交更新后的.version并推送分支;
  2. .github/workflows/release.yml(CI 执行)—— 等分支上的 CI 通过后手动触发(workflow_dispatch),负责构建全平台安装包和 wheel,并可选地对 macOS/Windows 产物签名、创建 GitHub 草稿 Release、发布 wheel 到 TestPyPI 与 PyPI。

两个阶段的分工可以这样理解:prepare 脚本负责"决定发什么版本"并产出带版本号的提交,release 工作流负责"把该提交变成真正的发布物"release.yml使用了名为release的 concurrency group,且cancel-in-progress: false,因此同一时间只允许一个发布构建运行,不会出现两个发布互相覆盖的情况。

流程时序图

Prepare 脚本的环境要求

tools/prepare_release.py在维护者本机运行,因此对环境有明确要求:

要求说明
干净的工作区脚本第一步执行git status --porcelain检查,存在未提交变更会直接报错please commit any outstanding changes first
已认证的ghCLI需要 push 权限访问ankitects/anki以及两个翻译仓库(ftl/core-repo/coreftl/qt-repo/desktop
rsync在 PATH 中用于把翻译模板拷贝到翻译仓库
packaging模块的 Python项目通过uv环境提供该模块

从源码看,脚本的sync_translations()会先对两个翻译模块执行git checkout main+git pull origin main(拉取最新翻译),再用rsync -ai --deleteftl/coreftl/qt下的模板同步过去,有变更则提交并推送Update templates脚本在提交.version之前就会推送翻译模板提交,所以如果这一步之后失败,翻译仓库的推送已经生效;但重新运行脚本是安全的——当没有新内容需要拷贝时,翻译步骤是空操作(git commit遇到 "nothing to commit" 会静默跳过)。

版本号格式:日历化 + PEP 440

Anki 采用日历版本(calendar versioning),并遵循 PEP 440 规范。仓库根目录的.version文件当前内容为26.08.1,格式规则由 tools/validate_version.py 中的正则^\d+\.(\d{2})(\.\d+)?(a\d+|b\d+|rc\d+)?$强制约束:

  • YY.MM:稳定版,如26.04
  • 可选.patch:如26.04.1
  • 预发布后缀a1(alpha)、b1(beta)、rc1(release candidate);
  • 月份必须两位补零\d{2}且校验月份 ≤ 12),即26.05合法、26.5非法。

官方文档给出的示例:26.05b1(beta)、26.05rc1(候选版)、26.05(稳定版)、26.05.1(补丁版)。validate_version还会用packaging.version.Version做 PEP 440 解析,并强制要求新版本号必须大于当前.version,否则抛出version must be greater than current ...

prepare_release.py中的release_branch()从版本号推导分支名:取base_versionrelease部分并把月份补零后拼成release/YY.MM,例如26.08b1release/26.08。因此脚本没有 branch 参数,分支完全由版本号决定

Release 分支工作流

所有版本都从release/YY.MM分支切出。分支名只含主版本号(YY.MM),不含预发布后缀——同一个周期的 beta、rc、稳定版都从同一条分支产出。

标准发布流程

# 1. 从 main 创建 release 分支并推送 git checkout -b release/26.05 main git push origin release/26.05 # 2. CI 在 push 到 release/** 分支时自动运行 # 3. 准备发布(在分支上更新 .version) just release::prepare --version 26.05b1 # 4. 在 TestPyPI 上验证 just release::testpypi --ref release/26.05 # 5. 发布完整版本 just release::public --ref release/26.05 # 6. 同一周期的后续预发布或稳定版:用新版本号重复步骤 3-5 # (例如 26.05b2、26.05rc1、26.05) # 7. 稳定版发布后,把 release 分支合并回 main,带回 .version 更新和 cherry-pick 的修复 git checkout main git merge release/26.05 git push origin main # 8. 稳定版发布后删除 release 分支

安全修复与热修复(hotfix)发布

安全修复流程与标准流程的关键差异:先建 Security Advisory + 临时私有 fork,在私有 fork 里走正常 PR 流程修 bug,在修复就绪前不公开 PR 也不发布 advisory。修复就绪后的步骤:

# 1. 从最新 release tag 创建 release 分支 git checkout -b release/26.05 26.05 # 2. 把修复 cherry-pick 到 release 分支 # 3. 推送分支并等待 CI git push origin release/26.05 # 4. 准备并发布 just release::prepare --version 26.05.1 just release::public --ref release/26.05 # 5. 合并回 main # 6. 安全补丁:发布 advisory 并视情况致谢报告者

热修复场景下 prepare 脚本可以通过--skip-ci-check跳过 CI 检查,release.ymlskip-ci-check输入项在注释中明确写明"for hotfix releases from non-main branches"。

Release 工作流的任务拓扑

.github/workflows/release.ymlprepare作业首先运行三个门禁检查:拒绝不兼容输入(draft-release=truesign=false时报错Draft releases must be signed)、校验版本与.version是否一致、检查 CI 结论和重复 tag/release。随后各平台构建作业并行执行:

值得注意的实现细节(来自 release.yml 源码):

  • macOS ARM/Intel 分成两个独立 job 而非矩阵:注释说明二者"很可能分叉"(签名怪癖、Xcode 版本、交叉编译标志、Rosetta 兼容处理),条件环境表达式已经足够复杂,再加矩阵维度会更难维护;
  • Windows ARM 走四段 job 链:因为azure/artifact-signing-action不支持 ARM runner,必须"ARM 构建 → x64 签 EXE → ARM 打包 MSI → x64 签 MSI";
  • Linux x86 与 ARM 分开:wheel 构建用 ubuntu-22.04(目标 glibc 2.35),ARM 安装包用 ubuntu-24.04-arm(Qt wheels 只提供 24.04+ 的 ARM 包,且 Briefcase 不捆绑 glibc,导致 ARM 安装包运行时要求 glibc 2.39+,比 x86 安装包的 2.35 更高);
  • Linux 构建作业还会编译打包 fcitx5-qt(输入法框架插件),保证 Linux 用户的中文等输入法体验。

输入参数详解

prepare_release.py 参数

脚本只接受一个必填的version位置参数和一个可选的--skip-ci-check标志。用法:

python3 tools/prepare_release.py 26.06 python3 tools/prepare_release.py 26.06b1 --skip-ci-check

release.yml 的 workflow_dispatch 输入

输入效果
sign对 macOS 和 Windows 产物签名。需要release环境。为 false 时对应 job 上传未签名产物且不接触签名密钥
draft-release创建带自动生成发布说明和安装包的 GitHub 草稿 Release。要求sign=truerelease环境、CI 通过(除非跳过)、无重复 tag/release、version.version一致
publish-testpypi发布 wheel 到 TestPyPI。需要release环境
publish-pypi发布 wheel 到 PyPI。需要release环境、CI 通过(除非跳过)、version.version一致;会先运行并等待 TestPyPI 发布 job;除非同时开启draft-release,否则不要求签名
skip-ci-check跳过 CI 状态检查,用于热修复发布
versiondraft-releasepublish-pypi必须与.version匹配;对纯构建、仅签名或仅 TestPyPI 的运行会被忽略(自动使用分支上的.version

所有布尔输入默认false非发布运行直接使用仓库中已有的.version,因此不需要 prepare 步骤也能构建。一次正常公开发布需要开启前四个布尔量:

sign=true, draft-release=true, publish-testpypi=true, publish-pypi=true

从源码看preparejob 的校验逻辑:只有draft-releasepublish-pypi为 true 时才认为这是 public release,此时才强制version输入与.version文件一致,并执行 CI 检查与重复检查;其余情况会打印Non-release run — ignoring version input并回退使用.version

环境审批门禁(Environment Gates)

release.yml使用 GitHubenvironments作为人工审批门禁。凡是接触签名凭据或发布产物的 job 都需要评审人先批准部署:

  • release环境:当signdraft-releasepublish-testpypipublish-pypi任一开启时启用。保护代码签名密钥、release token、PyPI/TestPyPI 的 trusted publishing/OIDC 凭据;
  • testpypi/pypi环境:分别保护 TestPyPI 与 PyPI 的 OIDC 发布权限(publish-testpypijob 声明id-token: write权限并使用pypa/gh-action-pypi-publish走 trusted publishing,不依赖 API token);
  • sign关闭时,macOS 与 Windows 构建 job 的环境表达式为${{ inputs.sign == true && 'release' || '' }},即不绑定release环境——它们不需要审批、也无法访问签名密钥。

用 just 运行发布

release.just中定义的release模块封装了 prepare 脚本和release.yml的触发命令。release::prepare在本地运行且不带--ref;其余所有 recipe 都是派发release.yml必须显式传入指向 release 分支的--ref参数version变量自动取自.version文件(version := \cat .version``)。

Recipe等价于说明
just release::prepare --version <ver>本地运行python3 tools/prepare_release.py校验版本、检查 CI、更新.version并推送
just release::build --ref <branch>gh workflow run release.yml+ 全部 false全平台构建,不签名不发布
just release::sign --ref <branch>同上 +sign=true构建并签名产物
just release::testpypi --ref <branch>同上 +publish-testpypi=true构建并发布到 TestPyPI
just release::pypi --ref <branch>同上 +publish-testpypi=true, publish-pypi=true发布到 TestPyPI 和 PyPI
just release::public --ref <branch>同上 +sign=true, draft-release=true, publish-testpypi=true, publish-pypi=true完整公开发布
just release::custom --ref <branch> --sign ... --draft-release ... --publish-testpypi ... --publish-pypi ... --skip-ci-check ...手动逐项指定所有开关最灵活的触发方式

运行just --list --list-submodules可查看全部可用 recipe 及参数。

从特性分支测试发布工作流

release.yml可以从任意分支派发用于测试。发布门禁只在开启draft-releasepublish-pypi时生效,因此测试构建非常安全:

# 1. 从你的分支派发 release.yml,所有布尔输入保持 false just release::build --ref <your-branch> # 2. 工作流按原样读取分支上的 .version(非发布运行忽略 version 输入),无需 prepare 步骤 # 3. 所有发布门禁(CI 检查、重复 tag 检查)都被跳过 # 4. 产物上传到 workflow run,但不会发布或打 tag

带代码签名测试

# 1. 在仓库 Settings → Environments → release 中,临时把你的分支加入 allowed deployment branches # 2. 派发工作流 just release::sign --ref <your-branch> # 3. 出现环境部署提示时批准 # 4. 测试完成后,把分支从环境 allowed branches 中移除

注意workflow_dispatch工作流只有在默认分支上存在该文件时才会出现在 GitHub Actions 的 UI 下拉菜单里。如果release.yml在你的分支上是新增或修改的,要用gh workflow run触发——合并回 main 之前 UI 里看不到它。

重要注意事项

  • release 工作流构建github.sha处的精确提交,它不写.version(那是 prepare 脚本的职责)。如果在 prepare 提交推送之前就派发 release,构建会使用派发时 HEAD 上的.version
  • draft-release=true+sign=false会被拒绝——草稿发布必须使用已签名的安装包;
  • publish-pypi=true时,wheel 先发布到 TestPyPI,TestPyPI job 成功后再发 PyPI;若同时设置draft-release=true,PyPI 发布还会等待草稿 GitHub Release 成功;
  • 预发布版本(如26.05b1)会在 GitHub 草稿 Release 上自动标记为 pre-releasepreparejob 用tools/validate_version.py判断is_prereleasereleasejob 据此追加--prerelease标志)。

发布后的通告与收尾

  • 草稿 Release 创建后,按需修改生成的 changelog,然后点击Publish release
  • 在 Anki 官方论坛的 Beta Testing 板块创建公告帖;稳定版发布后锁定该帖,并让用户到新帖反馈问题;
  • 稳定版发布后,更新ankitects/anki-landing-page仓库中的版本号。

音频包(anki-audio)的独立发布

音频播放与录制由独立的anki-audio包处理,位于 qt/audio(其pyproject.toml声明"Audio binaries (mpv, lame) for Anki",打包 mpv 与 lame 二进制)。它有自己的版本文件、构建脚本和发布工作流,与主发布管线解耦:

  1. 更新 Windows 预编译二进制:修改 build/configure/src/audio.rs 中的归档链接(该文件按平台给出 mpv v0.41.0 与 lame 3.100 的下载 URL 和 SHA256 校验值),然后运行./tools/ninja audio_wheel验证;
  2. 更新 macOS 构建脚本:修改 qt/audio/mpv.rb(Homebrew 打包脚本),本地用 qt/audio/build.sh 测试构建(该脚本也出现在.github/workflows/publish-audio-package.yml的 macOS job 中,Windows 平台则调用tools\ninja audio_wheel);
  3. 提升版本号:写入 qt/audio/.version——wheel 的版本号由 qt/audio/version.py 读取该文件得到;
  4. 提交 PR合入 main;
  5. 派发发布工作流.github/workflows/publish-audio-package.yml发布到 PyPI:
    gh workflow run publish-audio-package.yml -f sign-macos=true -f publish-testpypi=true -f publish-pypi=true

    该工作流在四个平台(macOS ARM/Intel、Windows x64/ARM)上矩阵构建 wheel,publish-pypi=true时强制要求sign-macos=true(macOS 二进制必须签名),同样走 TestPyPI → PyPI 的顺序;

  6. 更新主项目锁定版本:包发布后,把 qt/pyproject.toml 中固定的anki-audio依赖版本更新到新版本。

音频包之所以单独发布,是因为它在项目构建中作为外部依赖被引用(见build/configure/src/audio.rsaudio_wheel构建动作),主发布与音频包发布因此可以各自独立迭代、独立打版本。

结语

Anki 的发布体系把"版本校验、分支管理、翻译同步、CI 确认、跨平台构建、签名、多仓库发布、人工审批"全部串成一条可追溯的流水线:prepare 脚本保证发布的正确性(版本合法、无重复、CI 绿),release.yml的门禁与 job 拓扑保证产物的完整性与安全性(全平台覆盖、签名可控、OIDC 发布),而just release::*命令则把最常用的发布场景收敛成一行命令。无论是想理解日历版本发布的工程实践,还是需要为自己的开源项目搭建类似的自动化发布管线,这条链路都提供了非常完整的参考实现。

【免费下载链接】ankiAnki is a smart spaced repetition flashcard program项目地址: https://gitcode.com/GitHub_Trending/an/anki

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询