Files
zyt/app/research/release_packaging_final_audit.md
T
2026-08-11 09:12:51 +08:00

12 KiB
Raw Blame History

发布打包最终只读复审

日期:2026-08-11Asia/Shanghai
目标:判断是否可以从当前工作树重建 DoctorWorkstation 发布包。
边界:未执行完整 PyInstaller,未修改应用/打包源码;仅新增本复审报告。

最终结论

结论:当前工作树已具备启动原生完整重建的条件,未发现构建前 packaging 代码级 P0。

  • Windows 当前工作树可进入 Build_DoctorWorkstation.bat 完整重建;根发布入口的 -ValidateOnly 已通过。
  • macOS 的 9 个操作入口在 Git index 中现已全部为 100755,入口合同会同时检查文件系统 -x 和 Git index mode,不再被 Windows/NTFS 的可执行位模拟掩盖。
  • spec 已显式收集 PySide6.QtMultimedia/QtMultimediaWidgets,并安装独立 PyInstaller runtime hook。
  • Windows/macOS build 均先检查冻结媒体文件,再依次执行 --media-smoke-test--smoke-testpackage 仅在 build 成功后归档和计算哈希。
  • README、packaging README 与 .env.example 已说明 build/package 区别、双媒体门禁、macOS mode 以及 .env 不进入发布包的部署策略。

本结论只表示“可以开始原生重建”,不表示两个平台产物已经生成或可以立即对外发布。旧 dist 仍是修复前成品,必须由新构建替换;macOS .app 仍只能在原生 macOS 上产出和验收。

P0:无

按本轮定义——是否能从当前本地工作树发起完整重建——未发现 P0。

特别澄清:src/doctor_workstation/ui/diagnosis_media.pypackaging/runtime_media_smoke.pytests/test_packaging_media_gate.py 等文件目前未跟踪,但它们真实存在于当前工作树,spec/测试/发布入口都能读取,因此不是本地工作树构建阻断。它们的未提交状态属于跨机和后续复现风险,列为 P1,而不是本轮 P0。

P1:完整发布前仍须完成

P1-1:必须运行新的原生完整构建,旧 dist 不得作为验收证据

当前 dist/DoctorWorkstation 和版本 ZIP 早于 QtMultimedia 修复,仍缺少新媒体模块/插件。根“一键运行”会优先启动这个旧 EXE,所以完整重建前不要用它判断当前代码。

Windows 必须重新运行:

.\Build_DoctorWorkstation.bat

只有控制台依次报告以下门禁通过,且随后生成新 ZIP/哈希,才算 Windows 重建成功:

  1. QtWebEngine helper/resources 与 TRTC companion 文件检查;
  2. Frozen Qt multimedia file gate passed
  3. Frozen Qt multimedia smoke gate passed (--media-smoke-test, ...)
  4. Frozen application entry smoke gate passed (--smoke-test, ...)
  5. Windows package complete

本复审按约束未运行完整 PyInstaller,因此不能提前宣称这些冻结态门禁已经在新产物上通过。

P1-2:macOS 必须在原生目标架构主机完成构建

Windows 只能验证 shell 语法和合同,不能交叉产生 .app、macOS framework、媒体插件或有效签名。必须在 arm64 或 x86_64 macOS 构建机执行:

CI=1 DOCTOR_NONINTERACTIVE=1 bash scripts/check_macos_entrypoints.sh
./一键打包.command

成功证据必须包括媒体文件 gate、两个冻结 smoke gate、codesign --verify、实际架构、版本 ZIP 与 .sha256。这是一项平台执行门禁,不是当前脚本缺陷。

P1-3:当前工作树尚未形成可跨机复现的发布 commit/tag

当前本机直接构建会包含未跟踪媒体源码和 runtime hook,因此可以构建;但新的 clone、CI runner 或另一台 macOS 机器不会从当前 HEAD 获得这些文件。当前还有多处修改、staged mode 变化和大量测试产物。

因此:

  • 内部本机技术构建可以现在开始;
  • 跨机 macOS 构建或正式发布前,发布负责人仍应审阅 intended source,提交必要文件及 9 个 mode 变化,并从确定的 commit/tag 构建;
  • 不应使用 git add -A.pytest-tmp-*artifacts/ 等临时产物一并纳入。

这是正式发布的可复现性 P1,但按根任务要求不作为“当前工作树本地重建”P0。

P1-4:生产签名、公证链仍是发布策略门禁

  • Windows package 生成 ZIP 与 SHA-256,但没有集成 Authenticode。
  • macOS 接受可选 MACOS_CODESIGN_IDENTITY 并运行 codesign --verify,但不自动 notarize/staplead-hoc 签名也可能通过完整性验证。

内部 QA 包可以明确标记为内部构建。生产/院外分发则必须在归档前完成 Windows 签名;macOS 必须完成 Developer ID、嵌套 QtWebEngine helper 检查、notarization 和 stapling。staple 会改变 .app,之后要重新生成最终 ZIP 与 SHA-256。

P1-5:媒体 gate 不替代真实播放和 TRTC 准入

--media-smoke-test 已真实创建 Qt 媒体对象并检查 decoder backend,但没有加载具体 MP4/HLS,也不请求摄像头/麦克风或加入 TRTC 房间。正式验收仍需在最终冻结包上覆盖:

  • 获准分发的 MP4,产品要求时再覆盖 HLS;
  • 画面渲染与音频输出;
  • QtWebEngine companion/WebChannel
  • 摄像头、麦克风、扬声器切换;
  • TRTC 测试房间、弱网、设备插拔和休眠恢复。

P2:建议改进

  1. 仓库仍无 .gitattributes,本机 core.autocrlf=true;当前 shell 文件是 LF 且 bash -n 通过,但建议固定 *.sh/*.command 为 LF。
  2. Windows 使用 uv sync --frozenmacOS 使用 uv sync --locked;建议补 uv lock --check 或统一 lock 新鲜度策略。
  3. Windows version_info.txt0.1.0.0 与 pyproject 0.1.0 当前一致,但仍是人工同步,建议增加版本一致性门禁。
  4. Build_DoctorWorkstation.bat -ValidateOnly 仍输出 Package ready in: ...,容易让人误以为已经生成新包;退出码和行为本身正确。
  5. Windows/macOS 的哈希文件命名分别为 SHA256SUMS.txt<zip>.sha256,可统一以简化发布自动化。
  6. spec 尚未设置原生 .ico/.icns;不影响构建或媒体能力,但正式品牌发布可补齐。
  7. research/release_packaging_audit.mdrelease_packaging_gate_fixed.md 是阶段性历史记录,其中关于旧 mode/旧产物的描述不能替代本最终复审。
  8. AGENTS.md 引用的 .trellis/workflow.md.trellis/spec/ 在当前工作区仍不存在;属于治理缺口,不是本地构建阻断。

独立复审结果

1. PyInstaller specPASS

packaging/doctor_workstation.spec 现在:

  • 显式 hidden import
    • PySide6.QtMultimedia
    • PySide6.QtMultimediaWidgets
  • 继续显式收集 QtWebEngine 模块;
  • video_companion/dist 放入 video_companion_dist
  • 要求 packaging/runtime_media_smoke.py 必须存在;
  • runtime_hooks=[str(MEDIA_SMOKE_HOOK)] 安装媒体 gate。

当前 PyInstaller 6.22 的官方 hook-PySide6.QtMultimedia.py 会追加 Widgets 模块,Qt6 module mapping 会收集 plugins/multimedia,与本 spec 的显式 imports 一致。

2. --media-smoke-testPASS

packaging/runtime_media_smoke.py 作为 runtime hook,在普通启动时无副作用;仅当 argv 含 --media-smoke-test 时才在应用入口前执行:

  • 导入 QMediaPlayerQAudioOutputQMediaFormatQVideoWidget
  • 创建并连接 player/audio/video 对象;
  • player.isAvailable() 必须为真;
  • Decode 模式至少暴露一种支持格式;
  • 显示 16×16 offscreen video widget,并完成一次 Qt event loop
  • 任一错误固定返回 70,成功返回 0。

独立测试验证了三种状态:普通 argv 惰性退出 0;隔离掉 site-packages 后媒体 argv 返回 70;当前 PySide6 环境 offscreen 实例化返回 0。

3. Windows build/packagePASS(静态与轻量合同)

scripts/build_windows.ps1 在 PyInstaller 后要求以下文件:

  • QtMultimedia.pyd
  • QtMultimediaWidgets.pyd
  • Qt6Multimedia.dll
  • Qt6MultimediaWidgets.dll
  • plugins/multimedia/ffmpegmediaplugin.dll
  • plugins/multimedia/windowsmediaplugin.dll

随后先运行 --media-smoke-test,再运行原 --smoke-test;两者均沿用隔离配置目录、loopback-only proxy、offscreen、30 秒超时、退出码和未捕获异常日志检查。

scripts/package_windows.ps1 把 runtime hook 加入 required files,调用 build 之后才复制 release launcher、压缩 ZIP 和生成 SHA-256。Build_DoctorWorkstation.bat -ValidateOnly 本轮通过。

4. macOS build/packagePASS(静态与轻量合同)

scripts/build_macos.sh 在成功前要求:

  • PySide6.QtMultimediaQtMultimediaWidgets Python .so
  • 对应 framework,或适配 dylib 布局的后备检查;
  • plugins/multimedia 目录;
  • 至少一个 *mediaplugin*.dylib/.so backend
  • QtWebEngine helper/resources、companion 与主可执行文件。

之后执行 codesign --verify,再顺序运行媒体 smoke 和应用 smoke。scripts/package_macos.sh 在准备依赖前先运行入口合同,并只在 build 全部成功后用 ditto 归档和计算哈希。

这些路径模式与当前 PySide6/PyInstaller 的 macOS framework/plugin 布局相容;最终仍需原生构建确认。

5. macOS Git executable modePASS

以下 9 个文件的 index mode 本轮均为 100755

  1. package_macos.command
  2. run_macos.command
  3. 一键打包.command
  4. 一键运行.command
  5. scripts/build_macos.sh
  6. scripts/check_macos_entrypoints.sh
  7. scripts/macos_helpers.sh
  8. scripts/package_macos.sh
  9. scripts/run_macos.sh

scripts/check_macos_entrypoints.sh 已将自身纳入 operational files,逐个执行 bash -n、检查实际 -x,在 Git 工作树中还读取 index mode 并要求 100755。本轮 Git Bash 合同执行通过。

6. README 与 .envPASS

  • 根 README 正确区分 build_*(只生成/验证 onedir 或 .app)和根/package 入口(生成版本 ZIP 与哈希)。
  • Windows/macOS 一键打包描述已包含 QtWebEngine/QtMultimedia 文件门禁与两个冻结 smoke。
  • README 明确 macOS 操作文件必须以 mode 100755 跟踪。
  • .env.example 与 README 明确该文件不进入 ZIP/.app;生产配置应由受控 launcher/MDM/进程环境注入。
  • 文档明确禁止把密码、token、UserSig、TRTC SecretKey 等凭据放进 .env 或发布包。

7. 输出合同:PASS(待新构建兑现)

输出命名未被修复破坏:

  • Windows onedirdist/DoctorWorkstation/
  • Windows ZIPdist/DoctorWorkstation-Windows-x64-<version>.zip
  • Windows checksumdist/SHA256SUMS.txt
  • macOS bundledist/DoctorWorkstation.app
  • macOS ZIPdist/DoctorWorkstation-macOS-{arm64|x64}-<version>.zip
  • macOS checksum<zip>.sha256

两个 package 流程都位于 build/gate 之后,失败不会进入新的归档步骤。

本轮实际执行的轻量检查

检查 结果
Windows 三份 PowerShell parser PASS
Build_DoctorWorkstation.bat -ValidateOnly PASS
全部 macOS shell/command bash -n PASS
scripts/check_macos_entrypoints.sh(含 index mode PASS
packaging media gate + one-click + entrypoint pytest 10 passed
runtime hook/packaging test Ruff PASS
relevant tracked diff whitespace check PASS

pytest 首次执行曾因沙箱无权枚举系统 pytest-of-pc 临时目录而产生 1 个 setup error;改用工作区内独立临时目录重跑后 10 项全部通过,并已安全清理该目录。该环境错误不是代码失败。

主代理下一步

当前 Windows 工作树

.\Build_DoctorWorkstation.bat -ValidateOnly
.\Build_DoctorWorkstation.bat

完成后不要只看退出码;确认新 onedir/ZIP 时间戳、媒体文件、两个 smoke gate 日志及 SHA-256,并在无开发环境机器上做真实媒体/TRTC 验收。

原生 macOS

先确保目标机器拿到与当前工作树等价的全部 intended files(正式发布推荐先形成 commit/tag),然后:

git ls-files --stage -- '*.command' 'scripts/*.sh'
CI=1 DOCTOR_NONINTERACTIVE=1 bash scripts/check_macos_entrypoints.sh
export MACOS_CODESIGN_IDENTITY='Developer ID Application: <组织名称> (<TEAMID>)'
./一键打包.command

随后验证 .app 架构、媒体 framework/plugin、两个 smoke gate、签名、公证/staple、最终 ZIP 哈希和真实设备/TRTC 流程。