330 lines
20 KiB
Markdown
330 lines
20 KiB
Markdown
# DoctorWorkstation 发布打包就绪审计
|
||
|
||
审计日期:2026-08-11(Asia/Shanghai)
|
||
审计主机:Windows 10/11 工作区 `D:\web\zyt\app`
|
||
审计边界:只检查入口、脚本、PyInstaller spec、冻结依赖、伴随页、文档和既有产物;未执行完整 PyInstaller,未改业务/打包源码。
|
||
|
||
## 结论
|
||
|
||
**当前状态:不可直接发布既有 `dist`,也不应立即从当前 Git 状态制作正式发布包。**
|
||
|
||
构建流水线的主体是完整的:Windows/macOS 均有根目录中英文入口,能从锁文件准备 Python/Node 依赖,能构建 TRTC Web companion,PyInstaller 使用 onedir/.app,构建后会检查 QtWebEngine 与 companion,并在隔离目录中运行冻结入口 `--smoke-test`。Windows 既有 ZIP 的命名和 SHA-256 也正确。
|
||
|
||
但发布前有三个 P0:macOS 入口在 Git 中没有可执行位;新增 QtMultimedia 业务文件仍未纳入版本控制且工作树存在大量未提交发布输入;当前 Windows 成品早于新增媒体代码,缺少 QtMultimedia Python 模块、Widgets 库及媒体插件。另有 P1:当前冻结烟测会在媒体能力缺失时仍通过,生产级签名/公证也尚未进入一键流水线。
|
||
|
||
**macOS 特别说明:PyInstaller 不能从 Windows 交叉产出或验收 `.app`。本审计只能检查 macOS 脚本和合同;最终 `.app`、原生 Qt/媒体插件、签名、摄像头/麦克风权限及 TRTC 实通必须在原生 macOS(对应 arm64 或 x86_64)上构建和验证。**
|
||
|
||
严重度定义:
|
||
|
||
- P0:在任何正式构建/分发前必须解决。
|
||
- P1:可做内部技术构建,但生产发布前必须解决或由发布负责人书面接受风险。
|
||
- P2:不阻断本次内部构建,建议纳入发布工程改进。
|
||
|
||
## 构建前阻断
|
||
|
||
### P0-1:macOS Finder/命令行入口在 Git 索引中全部为 `100644`
|
||
|
||
`git ls-files --stage -- '*.command' 'scripts/*.sh'` 显示以下文件均为 `100644`,不是 `100755`:
|
||
|
||
- `run_macos.command`
|
||
- `package_macos.command`
|
||
- `一键运行.command`
|
||
- `一键打包.command`
|
||
- `scripts/build_macos.sh`
|
||
- `scripts/package_macos.sh`
|
||
- `scripts/run_macos.sh`
|
||
- `scripts/macos_helpers.sh`
|
||
- `scripts/check_macos_entrypoints.sh`
|
||
|
||
影响:新的 macOS clone 中,Finder 双击 `.command` 和 README 中的 `./scripts/build_macos.sh` 不能满足“一键运行/一键打包”;`scripts/check_macos_entrypoints.sh` 自己也要求这些入口 `-x`。Windows/NTFS 下 Git Bash 把文件视为可执行,因此当前合同检查通过并不能覆盖 Git 模式错误。
|
||
|
||
发布前处理:
|
||
|
||
```powershell
|
||
git update-index --chmod=+x run_macos.command package_macos.command `
|
||
"一键运行.command" "一键打包.command" `
|
||
scripts/build_macos.sh scripts/package_macos.sh scripts/run_macos.sh `
|
||
scripts/macos_helpers.sh scripts/check_macos_entrypoints.sh
|
||
git ls-files --stage -- '*.command' 'scripts/*.sh'
|
||
```
|
||
|
||
第二条命令的每一行都必须以 `100755` 开头,并将模式变化提交到发布 commit。
|
||
|
||
### P0-2:当前发布输入不在可复现的 Git 状态
|
||
|
||
审计时工作树含多处已修改和大量未跟踪代码;与本次媒体发布直接相关的 `src/doctor_workstation/ui/diagnosis_media.py` 及对应测试仍是未跟踪文件。若从当前目录构建,PyInstaller 会把这些本地文件打入包;若从当前 HEAD/干净 CI clone 构建,则不会包含它们。两者结果不一致。
|
||
|
||
发布前必须由负责人审阅 `git status --short`,只纳入确认属于本次版本的源码、测试、脚本和文档,并从确定的 commit/tag 构建。不要用 `git add -A` 掩盖当前大量测试产物和临时目录。
|
||
|
||
### P0-3:既有 Windows ZIP/onedir 早于新增 QtMultimedia 代码,禁止分发
|
||
|
||
证据:
|
||
|
||
- `dist/DoctorWorkstation/DoctorWorkstation.exe`:2026-08-10 17:07。
|
||
- `dist/DoctorWorkstation-Windows-x64-0.1.0.zip`:2026-08-10 17:08。
|
||
- `src/doctor_workstation/ui/diagnosis_media.py`:2026-08-11 01:05。
|
||
- 既有成品只有 `Qt6Multimedia.dll`/QML 辅助文件,缺少:
|
||
- `PySide6/QtMultimedia.pyd`
|
||
- `PySide6/QtMultimediaWidgets.pyd`
|
||
- `PySide6/Qt6MultimediaWidgets.dll`
|
||
- `PySide6/plugins/multimedia/ffmpegmediaplugin.dll`
|
||
- `PySide6/plugins/multimedia/windowsmediaplugin.dll`
|
||
|
||
既有 ZIP 也缺少同一组文件。它的 SHA-256 文件本身有效,但只证明旧 ZIP 未损坏,不证明它对应当前源码。
|
||
|
||
此外,根目录“一键运行”优先启动既有 `dist`,所以当前直接双击会运行旧代码,不会验证当前工作树。正式验收前必须完成干净重建,并以新 ZIP 的时间戳、文件清单和哈希替换旧证据。
|
||
|
||
## P1:生产发布前必须闭环
|
||
|
||
### P1-1:QtMultimedia 依赖可用,但冻结收集没有独立门禁
|
||
|
||
通过项:
|
||
|
||
- `pyproject.toml` 依赖 `PySide6>=6.8.2,<7`;`uv.lock` 当前锁定 PySide6/PySide6 Addons/Essentials 6.11.1,Addons 提供 QtMultimedia。
|
||
- 当前 `.venv` 和 `.venv-build` 均有 `QtMultimedia.pyd`、`QtMultimediaWidgets.pyd`、`Qt6MultimediaWidgets.dll` 以及 Windows `plugins/multimedia` 下的 FFmpeg/Windows Media 插件。
|
||
- 当前 PyInstaller 6.22 的官方 `hook-PySide6.QtMultimedia.py` 会追加 `PySide6.QtMultimediaWidgets`,Qt 模块映射会收集 Qt 6 的 `multimedia` 插件。当前源码中的静态 import 理论上会触发该 hook。
|
||
|
||
缺口:
|
||
|
||
- `packaging/doctor_workstation.spec` 的显式 hidden imports 只有 QtWebEngine 相关模块,没有显式列出 `PySide6.QtMultimedia`/`PySide6.QtMultimediaWidgets`。
|
||
- Windows/macOS 构建脚本只检查 QtWebEngine helper、Chromium `.pak` 和 `video_companion_dist/index.html`,不检查 QtMultimedia Python 扩展、Widgets 库或媒体后端插件。
|
||
- `diagnosis_media.py` 捕获 `ImportError` 并降级到外部打开,因此媒体模块缺失不会让应用启动失败。
|
||
- 实测旧冻结包缺少上述模块/插件,但隔离执行 `DoctorWorkstation.exe --smoke-test` 仍返回 0。这证明现有烟测不是 QtMultimedia 发布门禁。
|
||
|
||
发布前至少应做到以下二者之一,建议二者都做:
|
||
|
||
1. 在 spec 中显式纳入 `PySide6.QtMultimedia` 和 `PySide6.QtMultimediaWidgets`,继续使用 PyInstaller 官方 hooks 收集平台插件。
|
||
2. 在两个构建脚本中增加平台化的成品断言;Windows 检查两个 `.pyd`、`Qt6MultimediaWidgets.dll` 和 `plugins/multimedia`,macOS 检查两个 Python 扩展、Qt multimedia framework/dylib 和 `plugins/multimedia`。
|
||
|
||
最终验收还要用一个获准分发的 MP4(以及产品真实需要时的 HLS)实际播放,不能只看文件存在。
|
||
|
||
### P1-2:`--smoke-test` 只证明冻结启动,不证明 WebRTC/媒体可用
|
||
|
||
当前烟测的隔离、30 秒超时、非零退出码和未捕获异常日志检查设计良好;Windows/macOS 都不会访问真实后端。它会启动 composition root,并在约 1.2 秒后退出。
|
||
|
||
它不执行以下能力:
|
||
|
||
- 创建 `QMediaPlayer` 并确认媒体 backend 被发现;
|
||
- 播放音视频并检查解码/渲染;
|
||
- 打开 `QWebEngineView` 的 companion 主文档;
|
||
- 执行 `TRTC.isSupported()`、请求摄像头/麦克风或加入真实测试房间;
|
||
- 验证设备插拔、休眠恢复、弱网、屏幕共享或录播 codec。
|
||
|
||
因此冻结烟测应保留为启动门禁,同时新增“冻结媒体插件合同”和原生机器上的手工/自动媒体准入。README 已提示首发要做真实 TRTC/设备验收,这项不能被 `--smoke-test` 替代。
|
||
|
||
### P1-3:生产分发签名链未纳入一键打包
|
||
|
||
Windows:当前流水线生成 ZIP 与 SHA-256,但没有 Authenticode 签名步骤。SHA-256 提供完整性,不提供发布者身份;对外分发前应在压缩前签名并验证最终可执行文件。
|
||
|
||
macOS:spec 接受可选的 `MACOS_CODESIGN_IDENTITY`,构建脚本只运行 `codesign --verify --deep --strict`。未提供 Developer ID 时,PyInstaller 的临时/ad-hoc 签名也可能通过这一完整性检查;脚本不执行 notarization/stapling,也没有验证嵌套 `QtWebEngineProcess.app` 的实际 entitlements/运行能力。当前 README 仅把签名、公证作为人工说明。
|
||
|
||
若本次只是内部 QA 包,应明确标注“未签名/未公证内部构建”。若是生产或院外分发,应先把 Windows 签名,以及 macOS Developer ID → nested code 检查 → notarization → stapling → 重新生成最终 ZIP/哈希,纳入发布流水线。注意:staple 会改变 `.app`,所以不能继续分发公证前生成的 ZIP。
|
||
|
||
### P1-4:macOS 成品尚无原生验证证据
|
||
|
||
Windows 上的 `bash -n` 和合同脚本只能证明 shell 文本结构。以下证据必须在 macOS 构建机补齐:
|
||
|
||
- `.app` 原生架构与最低系统版本;
|
||
- `QtWebEngineProcess.app`、Qt framework、QtMultimedia 插件实际存在并签名有效;
|
||
- `NSCameraUsageDescription`/`NSMicrophoneUsageDescription` 能触发正确权限流程;
|
||
- companion 的本地 `file:`/qrc 资源、WebChannel 和 TRTC 能在打包态工作;
|
||
- arm64/x64 输出名与实际二进制架构一致;
|
||
- 最终 ZIP 的 Gatekeeper/notary 结果。
|
||
|
||
这不是 Windows 端可以规避的检查;必须安排一台原生 macOS runner/构建机。
|
||
|
||
## P2:建议改进
|
||
|
||
1. 仓库没有 `.gitattributes`,而本机 `core.autocrlf=true`。当前 macOS shell 文件实际是无 BOM 的 LF,`bash -n` 通过,但建议用 `.gitattributes` 固定 `*.sh`/`*.command` 为 LF,并固定批处理/PowerShell 的预期行尾。
|
||
2. Windows 使用 `uv sync --frozen`,macOS 使用 `uv sync --locked`。当前 `uv.lock` 包含所需依赖,但 `--frozen` 不承担 lock 新鲜度检查;发布前可增加 `uv lock --check`,或统一为能拒绝 pyproject/lock 漂移的策略。
|
||
3. `packaging/windows/version_info.txt` 的 `0.1.0.0` 与 `pyproject.toml` 的 `0.1.0` 当前一致,但它是手工同步;脚本没有发布前一致性检查。升级版本时可能出现 ZIP 名与 EXE 版本资源不一致。
|
||
4. README 顶部的一键入口描述正确,但“测试与打包”段落调用 `build_*`,只产生 onedir/.app,不产生版本化 ZIP/哈希。建议明确区分“构建”与“发布打包”,并把 `package_*`/根入口列为正式发布命令。
|
||
5. `.env.example` 对 demo/生产、SSL、TRTC secret 禁止项说明清楚,未包含长期凭据;但发布 ZIP 不包含 README 或 `.env.example`,README 也没有明确冻结包从哪里查找 `.env`。若企业部署依赖环境文件,应说明冻结态放置/注入方式;若只允许登录页保存非敏感配置,也应明确说明无需随包放 `.env`。
|
||
6. Windows 用固定 `dist/SHA256SUMS.txt`,macOS 用相邻的 `<zip>.sha256`;两者都可校验,但发布自动化和用户说明可以统一。`dist` 还可能保留旧版本 ZIP,根打包入口成功后打开整个目录,建议在发布清单中明确唯一应交付的文件。
|
||
7. `Build_DoctorWorkstation.bat -ValidateOnly` 会打印 `Package ready in: ...`,即使它只做了入口预检;不影响退出码,但容易被误认作已生成新包。
|
||
8. spec 没有设置 Windows EXE/macOS bundle 的原生应用图标;窗口内 SVG 正常,但系统文件/Finder 图标仍是默认值。若品牌发布有要求,应在最终签名前补齐 `.ico`/`.icns`。
|
||
9. AGENTS.md 指向的 `.trellis/workflow.md` 与 `.trellis/spec/` 在本工作区不存在;本次无法执行项目内 Trellis 规范审查。这不影响二进制启动,但属于发布治理缺口。
|
||
|
||
## 已通过的检查
|
||
|
||
| 检查项 | 结果 | 备注 |
|
||
|---|---:|---|
|
||
| Windows 根英文入口 `Build_DoctorWorkstation.bat -ValidateOnly` | PASS | Node/uv/必需文件预检通过 |
|
||
| Windows 根英文入口 `Run_DoctorWorkstation.bat -ValidateOnly` | PASS | 当前命中既有冻结 EXE;不代表源码新鲜 |
|
||
| Windows 两个中文别名 `-ValidateOnly` | PASS | 正确转发参数和退出码 |
|
||
| PowerShell parser | PASS | `build_windows.ps1`、`package_windows.ps1`、`run_windows.ps1` 均无解析错误 |
|
||
| macOS `bash -n` | PASS | helpers、run/package/build/check 与四个 `.command` 均通过 |
|
||
| `scripts/check_macos_entrypoints.sh` | PASS(有限) | Git Bash 下合同通过;不能覆盖 Git `100644` 问题 |
|
||
| 入口/冻结异常合同 pytest | PASS | `tests/test_one_click_entrypoints.py` + `tests/test_entrypoint.py`,4 passed |
|
||
| 当前 source QtMultimedia import | PASS | `QMediaPlayer`、`QAudioOutput`、`QVideoWidget` 均可导入;两个 Windows multimedia plugin 可见 |
|
||
| TRTC companion 工作树 | PASS | `video_companion` 无 Git 变更;dist 新于源码 |
|
||
| companion 相对资源 | PASS | `base: './'`;index 引用的 JS/CSS 均存在 |
|
||
| companion 打包副本 | PASS(旧包) | source dist 与旧 frozen 副本的 `index.html` SHA-256 相同 |
|
||
| spec companion/resources 映射 | PASS | `video_companion/dist -> video_companion_dist`、`resources -> resources` 与运行时查找一致 |
|
||
| QtWebEngine 收集策略 | PASS | 显式 imports 触发官方 hooks,构建脚本检查 helper 与 `.pak` |
|
||
| 冻结启动烟测机制 | PASS(范围有限) | 旧 EXE 在隔离 demo/offscreen 环境中 `--smoke-test` 退出 0;同时暴露媒体缺口未被检测 |
|
||
| Windows 输出名 | PASS(旧包) | `DoctorWorkstation-Windows-x64-0.1.0.zip` |
|
||
| Windows SHA-256 | PASS(旧包) | `SHA256SUMS.txt` 与实际 ZIP hash 一致 |
|
||
| 版本号一致性 | PASS | pyproject `0.1.0`;Windows resource `0.1.0.0` |
|
||
|
||
## 入口、spec 和输出合同摘要
|
||
|
||
### Windows
|
||
|
||
- 根入口:中文别名 → 英文 `.bat` → `scripts/package_windows.ps1`/`run_windows.ps1`。
|
||
- 运行:优先 `dist/DoctorWorkstation/DoctorWorkstation.exe`,否则使用 `.venv` 或 `uv sync --frozen` 后运行源码。
|
||
- 发布打包:Node 20+ → `uv sync --frozen --extra build` → `npm ci` → companion build → PyInstaller `--clean` → 静态成品检查 → 隔离冻结烟测 → ZIP → SHA-256。
|
||
- 产物:
|
||
- onedir:`dist/DoctorWorkstation/`
|
||
- 启动器:`dist/Start_DoctorWorkstation.bat`
|
||
- ZIP:`dist/DoctorWorkstation-Windows-x64-<version>.zip`
|
||
- 校验:`dist/SHA256SUMS.txt`
|
||
|
||
### macOS
|
||
|
||
- 根入口:四个 `.command` 解析自身目录后用 `/bin/bash` 调用 `scripts/run_macos.sh` 或 `scripts/package_macos.sh`。
|
||
- 运行:优先 `/usr/bin/open dist/DoctorWorkstation.app`,否则确保 uv、同步锁定依赖并运行源码。
|
||
- 发布打包:原生 Darwin 检查 → uv/Node 20+(必要时下载)→ `npm ci` → companion build → PyInstaller `.app` → QtWebEngine/companion 检查 → codesign verify → 隔离冻结烟测 → `ditto` ZIP → SHA-256。
|
||
- 产物:
|
||
- bundle:`dist/DoctorWorkstation.app`
|
||
- ZIP:`dist/DoctorWorkstation-macOS-{arm64|x64}-<version>.zip`
|
||
- 校验:同路径 `<zip>.sha256`
|
||
|
||
## 主代理最终构建与验证命令
|
||
|
||
以下命令应在 P0 修复、发布输入已审阅并固定到 commit/tag 后执行。不要把当前旧 `dist` 当成成功证据。
|
||
|
||
### 1. 通用发布前门禁
|
||
|
||
```powershell
|
||
git status --short
|
||
git diff --check
|
||
git ls-files --stage -- '*.command' 'scripts/*.sh'
|
||
```
|
||
|
||
要求:无意外修改/未跟踪发布输入;macOS 操作文件均为 `100755`。研究报告或明确允许的生成物可以存在,但不得混入发布源清单。
|
||
|
||
### 2. Windows:在 Windows x64 原生主机执行
|
||
|
||
轻量预检:
|
||
|
||
```powershell
|
||
.\Build_DoctorWorkstation.bat -ValidateOnly
|
||
.\.venv\Scripts\python.exe -m pytest -p no:cacheprovider `
|
||
tests\test_one_click_entrypoints.py tests\test_entrypoint.py
|
||
```
|
||
|
||
正式构建(该入口内部已经执行 locked dependency preparation、companion build、PyInstaller、冻结烟测、ZIP 和哈希):
|
||
|
||
```powershell
|
||
.\Build_DoctorWorkstation.bat
|
||
```
|
||
|
||
构建后强制文件门禁:
|
||
|
||
```powershell
|
||
$artifact = Resolve-Path 'dist\DoctorWorkstation'
|
||
$qt = Join-Path $artifact '_internal\PySide6'
|
||
$required = @(
|
||
(Join-Path $artifact 'DoctorWorkstation.exe'),
|
||
(Join-Path $qt 'QtMultimedia.pyd'),
|
||
(Join-Path $qt 'QtMultimediaWidgets.pyd'),
|
||
(Join-Path $qt 'Qt6Multimedia.dll'),
|
||
(Join-Path $qt 'Qt6MultimediaWidgets.dll'),
|
||
(Join-Path $qt 'plugins\multimedia\ffmpegmediaplugin.dll'),
|
||
(Join-Path $qt 'plugins\multimedia\windowsmediaplugin.dll'),
|
||
(Join-Path $artifact '_internal\video_companion_dist\index.html')
|
||
)
|
||
$missing = $required | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) }
|
||
if ($missing) { throw "Missing frozen release files:`n$($missing -join "`n")" }
|
||
if (-not (Get-ChildItem -LiteralPath $artifact -Recurse -File `
|
||
-Filter 'QtWebEngineProcess.exe' | Select-Object -First 1)) {
|
||
throw 'QtWebEngineProcess.exe missing'
|
||
}
|
||
if (-not (Get-ChildItem -LiteralPath $artifact -Recurse -File `
|
||
-Filter 'qtwebengine_resources*.pak' | Select-Object -First 1)) {
|
||
throw 'QtWebEngine resources missing'
|
||
}
|
||
```
|
||
|
||
版本化 ZIP 与哈希复核:
|
||
|
||
```powershell
|
||
$version = ([regex]::Match(
|
||
[IO.File]::ReadAllText((Resolve-Path 'pyproject.toml')),
|
||
'(?m)^version\s*=\s*"([^"]+)"'
|
||
)).Groups[1].Value
|
||
$zip = Resolve-Path "dist\DoctorWorkstation-Windows-x64-$version.zip"
|
||
$actual = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash
|
||
$expected = ((Get-Content -Raw -Encoding UTF8 'dist\SHA256SUMS.txt').Trim() -split '\s+')[0]
|
||
if ($actual -ne $expected) { throw 'Windows release checksum mismatch' }
|
||
Write-Host "Verified: $zip`nSHA-256: $actual"
|
||
```
|
||
|
||
最后在一台无开发环境的 Windows 10/11 x64 机器解压 ZIP,双击 `Start_DoctorWorkstation.bat`,完成登录/demo、内嵌 companion、获准 MP4/HLS 回放、摄像头/麦克风与一次真实测试房间验收。若为生产外发,必须先完成 Authenticode,再重新生成 ZIP 和 SHA-256。
|
||
|
||
### 3. macOS:只能在原生 macOS 主机执行
|
||
|
||
模式、语法和入口合同:
|
||
|
||
```bash
|
||
git ls-files --stage -- '*.command' 'scripts/*.sh'
|
||
test "$(git ls-files --stage -- '*.command' 'scripts/*.sh' | \
|
||
awk '$1 != "100755" { bad++ } END { print bad + 0 }')" = 0
|
||
bash -n scripts/macos_helpers.sh scripts/run_macos.sh \
|
||
scripts/package_macos.sh scripts/build_macos.sh \
|
||
scripts/check_macos_entrypoints.sh \
|
||
run_macos.command package_macos.command \
|
||
一键运行.command 一键打包.command
|
||
CI=1 DOCTOR_NONINTERACTIVE=1 bash scripts/check_macos_entrypoints.sh
|
||
```
|
||
|
||
内部 QA 构建可直接运行;生产构建应先提供真实 Developer ID:
|
||
|
||
```bash
|
||
export MACOS_CODESIGN_IDENTITY='Developer ID Application: <组织名称> (<TEAMID>)'
|
||
./一键打包.command
|
||
```
|
||
|
||
构建后检查:
|
||
|
||
```bash
|
||
app='dist/DoctorWorkstation.app'
|
||
test -x "$app/Contents/MacOS/DoctorWorkstation"
|
||
test -n "$(find "$app" -type f \( -name 'QtMultimedia.so' -o -name 'QtMultimedia.*.so' \) -print -quit)"
|
||
test -n "$(find "$app" -type f \( -name 'QtMultimediaWidgets.so' -o -name 'QtMultimediaWidgets.*.so' \) -print -quit)"
|
||
test -n "$(find "$app" -type f -path '*/plugins/multimedia/*' -print -quit)"
|
||
test -n "$(find "$app" -type f -name 'QtWebEngineProcess' -print -quit)"
|
||
test -n "$(find "$app" -type f -name 'qtwebengine_resources*.pak' -print -quit)"
|
||
test -n "$(find "$app" -type f -path '*video_companion_dist/index.html' -print -quit)"
|
||
codesign --verify --deep --strict --verbose=2 "$app"
|
||
codesign -d --entitlements :- "$app/Contents/MacOS/DoctorWorkstation"
|
||
file "$app/Contents/MacOS/DoctorWorkstation"
|
||
```
|
||
|
||
版本化 ZIP 与哈希复核:
|
||
|
||
```bash
|
||
version="$(awk -F '"' '/^version = "/ { print $2; exit }' pyproject.toml)"
|
||
case "$(uname -m)" in
|
||
arm64) arch='arm64' ;;
|
||
x86_64) arch='x64' ;;
|
||
*) echo 'Unsupported macOS architecture' >&2; exit 1 ;;
|
||
esac
|
||
zip="DoctorWorkstation-macOS-$arch-$version.zip"
|
||
(cd dist && shasum -a 256 -c "$zip.sha256")
|
||
```
|
||
|
||
生产外发还需用 `xcrun notarytool` 提交、等待成功并 `xcrun stapler staple`。staple 后要用 `ditto --keepParent` 重新生成最终 ZIP 并重算 `.sha256`,再以 `spctl --assess --type execute --verbose=4` 和一台干净 macOS 13+ 机器验收。最终必须实测摄像头、麦克风、扬声器切换、QtMultimedia 回放、QtWebEngine companion 和 TRTC 测试房间。
|
||
|
||
## 发布准入判定
|
||
|
||
满足以下条件才可把状态改为“可发布”:
|
||
|
||
1. macOS 操作文件的 Git 模式全部为 `100755`。
|
||
2. intended release source 已纳入确定 commit/tag,构建工作树无意外输入。
|
||
3. Windows 从该 commit 完整重建;新 ZIP 含 QtMultimedia Python/Widgets/插件、QtWebEngine 和当前 companion,哈希通过。
|
||
4. 原生 macOS 从同一 commit 完整重建;`.app` 含对应媒体/QtWebEngine 组件,架构、签名、权限、ZIP 哈希通过。
|
||
5. 两个平台冻结 `--smoke-test` 通过,且独立媒体/companion/TRTC 验收通过。
|
||
6. 若为生产外发,Windows Authenticode 与 macOS Developer ID/notarization/stapling 均在最终归档前完成。
|