第一次打出 DMG:能运行,离能发布还有多远
当砚能在开发目录里打开窗口,我很自然地想把它装进「应用程序」,像一个正常工具那样用起来。
第一次 macOS 预览发布,让这个想法多了不少具体步骤:运行时库、第三方声明、版本号、安装包、签名,以及从下载文件重新走一遍安装。
先说明这段记录的范围:讲的是仓库留存的 alpha.1 发布报告与 alpha.2 更新。本次写日志没有重新下载安装包,也没有重新确认远端下载状态。
先把测试门禁改成真的门禁
发布前审查发现,当时竞态检测失败后会回退跑普通测试。普通测试过了,整条命令仍可能看起来成功。
这会把"没检查到竞态"误读成"竞态检查通过"。后来改成竞态失败就失败;机器缺少可用 C 工具链时明确报 BLOCKED,并以失败退出。
常规程序使用 CGO_ENABLED=0 构建,与 race 检测需要 C 工具链并不冲突。两者解决的是不同问题。
高亮测试还遇到了一个更隐蔽的影响:race 检测让执行明显变慢,而 Chroma 的正则有固定超时。慢到触发超时后,词法结果也可能改变。因此要查清超时来自哪里,保留正常负载测试,再给 race 测试安排能有效完成的工作量。
报告还记录了增量高亮的反例:JS 模板字符串内部状态不能只从可见 token 推断。当前高亮允许返回不精确结果,再在停止输入后完整重算;测试需要检查这个实际契约,而不是假定每次增量都严格等价。
打包的是一个可独立运行的应用
Go 二进制之外,应用还需要图标、资源、第三方声明和终端使用的 libghostty-vt。
开发模式首次打开终端可能下载库,但安装包不能把"我的缓存里刚好有"当作依赖已交付。砚把库放进 .app,并在验证里检查它真的被使用。
第三方声明也从"仓库根目录有一个文件",变成实际随包分发的内容。生成工具读取发布二进制的构建信息,汇总链接到的 Go 模块许可,同时带上原生终端库相关声明。
移植代码的来源也需要保留。接入开源依赖带来的省力,后面对应的是明确版本、来源和分发材料的维护工作。
.app、DMG 和 zip 各有要核对的东西
砚的预览发布输出四类文件:
| 文件 | 用途 |
|---|---|
.dmg |
安装介质,内含应用和指向 Applications 的链接 |
.app.zip |
同一个应用的备用分发形式 |
SHA256SUMS |
下载后核对文件 |
THIRD_PARTY_LICENSES.txt |
第三方声明,也放进应用资源目录 |
发布脚本检查架构、库、版本和签名等信息;zip 还处理了扩展属性带来的打包问题。发布报告记录了上传后重新下载、比对哈希、从下载的 DMG 安装并经 LaunchServices 启动。
从 Finder 启动时,当前目录和环境不再是开发终端的那一套。能离开源码目录运行,才算往真正的桌面应用又走了一步。
签名通过,也可能被 Gatekeeper 拒绝
alpha.1 是 Apple Silicon 的预览包,使用 ad-hoc 签名,没有 Developer ID 公证。
报告里 codesign --verify 通过,spctl 评估拒绝,这两个结果同时存在。前者不能用来证明已经达到受信任分发的状态。
因此发布说明写清楚预览属性和安装限制。这里不把缺少公证包装成已经解决,也不把安装文档中的步骤当作真人已经操作成功的证据。
原生窗口测试和真人操作分开记
这次报告记录了打包应用的原生自测:会话恢复、真实焦点切换触发自动保存、外部修改冲突、终端运行与关闭、终端库不可用时编辑仍可继续。
其中不少操作由 selftest 构建调用内核 API 驱动。它确实运行在原生窗口里,但并不是用真实键盘一项项输入。系统拼音候选位置、保存对话框、Gatekeeper 的手工操作,仍在未验证列表里。
性能数字也只能随版本走。例如报告记录过约 127 MB RSS、60 MB physical footprint 和某次 30 秒时的 0% CPU 样本。这几个指标不能相互替换,也不能当成 alpha.6 的重新测量。
alpha.2 只补了一件很实用的小事
第二个预览版把版本标签放到状态栏和窗口标题:预览版、稳定版、开发版能直接分辨,完整版本与提交号仍在关于窗口。
这个改动很小,但反馈问题时终于能先回答"现在到底开的是哪个构建"。安装包开始迭代后,这种信息比想象中有用。
第一次发布之后,我开始把构建、测试、打包、安装、实际输入分别记账。它们接起来才是交付,任何一个绿色结果都替代不了剩下几步。