Skip to content

优化 Windows 自动打包耗时并保留正式发布的干净构建 #79

Description

@amazing-fish

背景

当前 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,说明本次并非单次异常抖动。

第一性原理拆分

  1. pip 缓存已经命中,日志中的大部分包均为 Using cached;瓶颈不是网络下载,而是每台新 Runner 仍需解压、复制并安装 PySide6 相关 Wheels。
  2. PySide6_AddonsPySide6_Essentialsshiboken6 等压缩包就接近 249MB,因此依赖安装稳定占用约 30s。
  3. PyInstaller 构建中:
    • Analysis 约 58s;
    • 单文件 PKG 封装约 18s;
    • 最终 EXE 写入不足 1s。
  4. 当前命令始终使用 --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 的间接依赖版本,至少覆盖 packagingpefilepyinstaller-hooks-contribpywin32-ctypesaltgraph,避免依赖文件未变化但实际安装结果发生漂移。
  • 可评估改用 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。

验收标准

  1. 缓存未命中的首次构建能够正常产出 dist/TODOList.exe
  2. 连续三次依赖未变化、仅修改应用代码的 main 构建中:
    • 后两次明确命中增量缓存;
    • “安装依赖 + 构建可执行文件”的中位耗时相较当前 112s 基线至少下降 20%,目标不高于 90s;
    • 如果缓存恢复成本抵消收益,应移除该缓存方案并在 Issue 中记录数据。
  3. 修改 requirements、Python 版本、spec 或打包参数后,旧缓存不得被当作精确命中使用。
  4. 修改 Python 源码、图标、字体或声音资源后,产物必须包含最新内容,不得复用陈旧文件。
  5. v* 标签构建日志明确执行干净全量分析,并继续产出单文件 TODOList.exe
  6. 主分支 Pre-release、正式 Release、手动 Artifact 的发布边界和标签规则保持不变。
  7. 提交前后至少各保留一组 Actions 耗时数据,并在 PR 中列出对比结果。
  8. 同步 README、anchor.md 中受影响的构建约定;若确认行为约定没有变化,明确记录复盘结论。

风险与防护

  • 陈旧缓存污染产物:正式标签始终全量干净构建;主分支缓存键覆盖解释器、依赖和打包配置。
  • 缓存体积过大反而变慢:以 Actions 实测为准,缓存没有稳定净收益时不保留。
  • 取消错误任务:并发分组必须区分主分支、标签和手动触发。
  • 依赖锁定遗漏:安装日志中不得再出现未受控的间接依赖版本漂移。

非目标

  • 不改为自托管 Runner。
  • 不把正式发布改成 --onedir
  • 不更换 PySide6 或 GUI 框架。
  • 不为了提速盲目排除 QtSvgQtMultimedia 或其他模块。
  • 不修改应用 UI、提醒音、存储格式或运行时行为。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions