12 KiB
发布打包最终只读复审
日期:2026-08-11(Asia/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-test;package 仅在 build 成功后归档和计算哈希。 - README、packaging README 与
.env.example已说明 build/package 区别、双媒体门禁、macOS mode 以及.env不进入发布包的部署策略。
本结论只表示“可以开始原生重建”,不表示两个平台产物已经生成或可以立即对外发布。旧 dist 仍是修复前成品,必须由新构建替换;macOS .app 仍只能在原生 macOS 上产出和验收。
P0:无
按本轮定义——是否能从当前本地工作树发起完整重建——未发现 P0。
特别澄清:src/doctor_workstation/ui/diagnosis_media.py、packaging/runtime_media_smoke.py、tests/test_packaging_media_gate.py 等文件目前未跟踪,但它们真实存在于当前工作树,spec/测试/发布入口都能读取,因此不是本地工作树构建阻断。它们的未提交状态属于跨机和后续复现风险,列为 P1,而不是本轮 P0。
P1:完整发布前仍须完成
P1-1:必须运行新的原生完整构建,旧 dist 不得作为验收证据
当前 dist/DoctorWorkstation 和版本 ZIP 早于 QtMultimedia 修复,仍缺少新媒体模块/插件。根“一键运行”会优先启动这个旧 EXE,所以完整重建前不要用它判断当前代码。
Windows 必须重新运行:
.\Build_DoctorWorkstation.bat
只有控制台依次报告以下门禁通过,且随后生成新 ZIP/哈希,才算 Windows 重建成功:
- QtWebEngine helper/resources 与 TRTC companion 文件检查;
Frozen Qt multimedia file gate passed;Frozen Qt multimedia smoke gate passed (--media-smoke-test, ...);Frozen application entry smoke gate passed (--smoke-test, ...);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/staple;ad-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:建议改进
- 仓库仍无
.gitattributes,本机core.autocrlf=true;当前 shell 文件是 LF 且bash -n通过,但建议固定*.sh/*.command为 LF。 - Windows 使用
uv sync --frozen,macOS 使用uv sync --locked;建议补uv lock --check或统一 lock 新鲜度策略。 - Windows
version_info.txt的0.1.0.0与 pyproject0.1.0当前一致,但仍是人工同步,建议增加版本一致性门禁。 Build_DoctorWorkstation.bat -ValidateOnly仍输出Package ready in: ...,容易让人误以为已经生成新包;退出码和行为本身正确。- Windows/macOS 的哈希文件命名分别为
SHA256SUMS.txt与<zip>.sha256,可统一以简化发布自动化。 - spec 尚未设置原生
.ico/.icns;不影响构建或媒体能力,但正式品牌发布可补齐。 research/release_packaging_audit.md与release_packaging_gate_fixed.md是阶段性历史记录,其中关于旧 mode/旧产物的描述不能替代本最终复审。- AGENTS.md 引用的
.trellis/workflow.md和.trellis/spec/在当前工作区仍不存在;属于治理缺口,不是本地构建阻断。
独立复审结果
1. PyInstaller spec:PASS
packaging/doctor_workstation.spec 现在:
- 显式 hidden import:
PySide6.QtMultimediaPySide6.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-test:PASS
packaging/runtime_media_smoke.py 作为 runtime hook,在普通启动时无副作用;仅当 argv 含 --media-smoke-test 时才在应用入口前执行:
- 导入
QMediaPlayer、QAudioOutput、QMediaFormat、QVideoWidget; - 创建并连接 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/package:PASS(静态与轻量合同)
scripts/build_windows.ps1 在 PyInstaller 后要求以下文件:
QtMultimedia.pydQtMultimediaWidgets.pydQt6Multimedia.dllQt6MultimediaWidgets.dllplugins/multimedia/ffmpegmediaplugin.dllplugins/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/package:PASS(静态与轻量合同)
scripts/build_macos.sh 在成功前要求:
PySide6.QtMultimedia与QtMultimediaWidgetsPython.so;- 对应 framework,或适配 dylib 布局的后备检查;
plugins/multimedia目录;- 至少一个
*mediaplugin*.dylib/.sobackend; - QtWebEngine helper/resources、companion 与主可执行文件。
之后执行 codesign --verify,再顺序运行媒体 smoke 和应用 smoke。scripts/package_macos.sh 在准备依赖前先运行入口合同,并只在 build 全部成功后用 ditto 归档和计算哈希。
这些路径模式与当前 PySide6/PyInstaller 的 macOS framework/plugin 布局相容;最终仍需原生构建确认。
5. macOS Git executable mode:PASS
以下 9 个文件的 index mode 本轮均为 100755:
package_macos.commandrun_macos.command一键打包.command一键运行.commandscripts/build_macos.shscripts/check_macos_entrypoints.shscripts/macos_helpers.shscripts/package_macos.shscripts/run_macos.sh
scripts/check_macos_entrypoints.sh 已将自身纳入 operational files,逐个执行 bash -n、检查实际 -x,在 Git 工作树中还读取 index mode 并要求 100755。本轮 Git Bash 合同执行通过。
6. README 与 .env:PASS
- 根 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 onedir:
dist/DoctorWorkstation/ - Windows ZIP:
dist/DoctorWorkstation-Windows-x64-<version>.zip - Windows checksum:
dist/SHA256SUMS.txt - macOS bundle:
dist/DoctorWorkstation.app - macOS ZIP:
dist/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 流程。