背景
当前 Windows 自动打包流程能够稳定产出单文件 TODOList.exe,但 GitHub-hosted Windows Runner 每次都从全新环境开始,依赖安装和 PyInstaller 全量分析占据了主要时间。
基线取自 workflow run #31144791428:
| 阶段 |
耗时 |
| 检出代码 |
9s |
| 恢复 pip 缓存 |
3s |
| 安装依赖 |
33s |
| 构建可执行文件 |
79s |
| 上传 Artifact |
3s |
| Job 总时长 |
约 134s |
近期其他成功构建中,依赖安装约为 24–34s,PyInstaller 构建约为 69–79s,说明本次并非单次异常抖动。
第一性原理拆分
- pip 缓存已经命中,日志中的大部分包均为
Using cached;瓶颈不是网络下载,而是每台新 Runner 仍需解压、复制并安装 PySide6 相关 Wheels。
- 仅
PySide6_Addons、PySide6_Essentials、shiboken6 等压缩包就接近 249MB,因此依赖安装稳定占用约 30s。
- PyInstaller 构建中:
- Analysis 约 58s;
- 单文件 PKG 封装约 18s;
- 最终 EXE 写入不足 1s。
- 当前命令始终使用
--clean,并且 Runner 是临时环境,因此每次都会显示 Analysis-00.toc is non existent,无法复用依赖图和中间产物。
相关流程:.github/workflows/build-exe.yml
目标
在不改变应用功能、不改变正式 Release 单文件产物形态的前提下:
- 缩短
main 推送后的预发布构建时间;
- 保留
v* 正式标签构建的干净、可复现语义;
- 提供明确的缓存命中和阶段耗时证据;
- 避免连续 merge 时为已经过期的主分支提交浪费构建资源。
建议实施范围
1. 依赖安装
- 移除每次运行的
python -m pip install --upgrade pip;Runner 已包含可用 pip,不应为补丁版本升级反复卸载和安装。
- 完整锁定 PyInstaller 的间接依赖版本,至少覆盖
packaging、pefile、pyinstaller-hooks-contrib、pywin32-ctypes 和 altgraph,避免依赖文件未变化但实际安装结果发生漂移。
- 可评估改用
actions/setup-python 内置的 pip 缓存以简化配置;该调整本身不作为主要提速收益。
- 不默认缓存整个虚拟环境;PySide6 展开后体积较大,必须用实际数据证明恢复缓存比重新安装更快后才能采用。
2. 主分支增量构建
- 为
main push 缓存 PyInstaller 的稳定 workpath/build 中间目录。
- 缓存键至少包含:
- Runner OS;
- 实际 Python 版本;
- requirements 文件哈希;
- spec/打包参数或等价配置哈希。
- 仅在
main 预发布构建中允许去掉 --clean 并使用增量缓存。
- 正式
v* 标签和 workflow_dispatch 恢复构建继续执行 --clean --onefile,不得依赖主分支中间产物。
- 缓存缺失、损坏或不兼容时必须自动退化为完整构建,而不是阻断发布。
3. 取消过期构建
- 为同一
main 分支增加独立的 concurrency 分组和 cancel-in-progress。
- 正式标签之间不得互相取消;手动恢复任务不得被普通主分支 push 取消。
4. 可观测性
在 Job Summary 中记录:
- pip/PyInstaller 缓存是否命中;
- 依赖安装耗时;
- PyInstaller 构建耗时;
- 总耗时;
- 当前构建属于增量预发布还是干净正式构建;
- 产物路径、大小和 SHA-256。
验收标准
- 缓存未命中的首次构建能够正常产出
dist/TODOList.exe。
- 连续三次依赖未变化、仅修改应用代码的
main 构建中:
- 后两次明确命中增量缓存;
- “安装依赖 + 构建可执行文件”的中位耗时相较当前 112s 基线至少下降 20%,目标不高于 90s;
- 如果缓存恢复成本抵消收益,应移除该缓存方案并在 Issue 中记录数据。
- 修改 requirements、Python 版本、spec 或打包参数后,旧缓存不得被当作精确命中使用。
- 修改 Python 源码、图标、字体或声音资源后,产物必须包含最新内容,不得复用陈旧文件。
v* 标签构建日志明确执行干净全量分析,并继续产出单文件 TODOList.exe。
- 主分支 Pre-release、正式 Release、手动 Artifact 的发布边界和标签规则保持不变。
- 提交前后至少各保留一组 Actions 耗时数据,并在 PR 中列出对比结果。
- 同步 README、
anchor.md 中受影响的构建约定;若确认行为约定没有变化,明确记录复盘结论。
风险与防护
- 陈旧缓存污染产物:正式标签始终全量干净构建;主分支缓存键覆盖解释器、依赖和打包配置。
- 缓存体积过大反而变慢:以 Actions 实测为准,缓存没有稳定净收益时不保留。
- 取消错误任务:并发分组必须区分主分支、标签和手动触发。
- 依赖锁定遗漏:安装日志中不得再出现未受控的间接依赖版本漂移。
非目标
- 不改为自托管 Runner。
- 不把正式发布改成
--onedir。
- 不更换 PySide6 或 GUI 框架。
- 不为了提速盲目排除
QtSvg、QtMultimedia 或其他模块。
- 不修改应用 UI、提醒音、存储格式或运行时行为。
背景
当前 Windows 自动打包流程能够稳定产出单文件
TODOList.exe,但 GitHub-hosted Windows Runner 每次都从全新环境开始,依赖安装和 PyInstaller 全量分析占据了主要时间。基线取自 workflow run #31144791428:
近期其他成功构建中,依赖安装约为 24–34s,PyInstaller 构建约为 69–79s,说明本次并非单次异常抖动。
第一性原理拆分
Using cached;瓶颈不是网络下载,而是每台新 Runner 仍需解压、复制并安装 PySide6 相关 Wheels。PySide6_Addons、PySide6_Essentials、shiboken6等压缩包就接近 249MB,因此依赖安装稳定占用约 30s。--clean,并且 Runner 是临时环境,因此每次都会显示Analysis-00.toc is non existent,无法复用依赖图和中间产物。相关流程:.github/workflows/build-exe.yml
目标
在不改变应用功能、不改变正式 Release 单文件产物形态的前提下:
main推送后的预发布构建时间;v*正式标签构建的干净、可复现语义;建议实施范围
1. 依赖安装
python -m pip install --upgrade pip;Runner 已包含可用 pip,不应为补丁版本升级反复卸载和安装。packaging、pefile、pyinstaller-hooks-contrib、pywin32-ctypes和altgraph,避免依赖文件未变化但实际安装结果发生漂移。actions/setup-python内置的 pip 缓存以简化配置;该调整本身不作为主要提速收益。2. 主分支增量构建
mainpush 缓存 PyInstaller 的稳定 workpath/build 中间目录。main预发布构建中允许去掉--clean并使用增量缓存。v*标签和workflow_dispatch恢复构建继续执行--clean --onefile,不得依赖主分支中间产物。3. 取消过期构建
main分支增加独立的concurrency分组和cancel-in-progress。4. 可观测性
在 Job Summary 中记录:
验收标准
dist/TODOList.exe。main构建中:v*标签构建日志明确执行干净全量分析,并继续产出单文件TODOList.exe。anchor.md中受影响的构建约定;若确认行为约定没有变化,明确记录复盘结论。风险与防护
非目标
--onedir。QtSvg、QtMultimedia或其他模块。