最近做了一件很具体的事:基于官方预编译 VSCodium,打了一个可以发给同事直接安装的 macOS 应用。名字叫 PM Pro,Bundle ID 是 com.xiezhixin.pmpro,内置 OpenAI Codex 插件,默认中文、默认浅色主题,最终产出一个 .dmg 文件,双击就能装好。
过程中的坑比想象中多,但最后都踩过去了。
目标是什么
一开始目标很简单:
- 用官方预编译 VSCodium,不从源码编译
- 改 App 显示名为
PM Pro - 修改 Bundle ID 为
com.xiezhixin.pmpro - 内置 Codex 插件
- 内置默认配置(中文 + 浅色主题)
- 打包成 DMG
做到一半才发现,macOS App 分发真正麻烦的地方不在"改名",而在 Electron helper、代码签名、公证和用户首次启动体验。
基础打包流程
基础流程是这样的:
- 下载官方 VSCodium macOS zip
- 解压得到
VSCodium.app - 重命名为
PM Pro.app - 修改
Contents/Info.plist - 解压 VSIX 扩展到 App 内部
- 写入默认用户配置
- 生成 DMG
关键元数据:
- App 名称:
PM Pro - Bundle ID:
com.xiezhixin.pmpro - VSCodium 版本:
1.116.02821 - Codex 插件:
openai.chatgpt 26.5422.71525 - 中文语言包:
MS-CEINTL.vscode-language-pack-zh-hans 1.110.2026050117(选了兼容^1.110.0的版本,因为最新版语言包要求 VS Code^1.118.1,不兼容当前 VSCodium 1.116)
坑一:改完 Info.plist 后不能再调用 codium CLI
最早的脚本里用的是:
codium --install-extension xxx.vsix --extensions-dir ...
但先改了 Info.plist 之后,原 App 签名就失效了。macOS 直接把 codium CLI 杀掉:
Killed: 9
解决方式是:VSIX 本质上是一个 zip,直接读取 extension/package.json,然后把 extension/ 目录解压到目标扩展目录即可,完全不需要启动 App。
扩展目录名按 VS Code 约定生成:publisher.name-version,例如 openai.chatgpt-26.5422.71525。
坑二:Electron Helper 必须一起改名
第一次能打出 DMG,但打开后马上崩溃,系统日志里有一行很关键:
Unable to find helper app
原因是 Electron macOS App 会根据主 App 名称查找 helper。主 App 改成了 PM Pro,但包里还叫:
VSCodium Helper.appVSCodium Helper (Renderer).appVSCodium Helper (GPU).appVSCodium Helper (Plugin).app
所以必须同步改成 PM Pro Helper.app 等,同时还要改每个 helper 的 CFBundleIdentifier、CFBundleName、CFBundleDisplayName、CFBundleExecutable,以及 Contents/MacOS/ 下的真实可执行文件名。
默认中文和浅色主题
默认配置有两个层次。
第一层是用户配置:
{
"workbench.colorTheme": "Light Modern"
}
第二层是语言:
{
"locale": "zh-cn"
}
但只放 argv.json 不够,实际启动时进程仍然可能是 --lang=en-US。最后做了一个启动器:把原来的 VSCodium 主程序改名为 VSCodium-bin,再生成一个新的 PM Pro 启动脚本。
启动器做三件事:
- 首次启动时,把默认配置复制到
~/Library/Application Support/PM Pro/User - 使用独立用户数据目录,避免污染 VSCodium 的配置
- 强制带
--locale zh-cn启动
关键启动命令:
exec "$REAL_EXECUTABLE" --locale zh-cn --user-data-dir "$USER_DATA_DIR" "$@"
DMG 安装体验
一开始生成 DMG 时只把 .app 放进去。技术上可用,但对不熟悉 macOS 的用户不友好。
后来改成标准安装盘结构:
ditto "$APPDIR" "$DMG_STAGING/${CUSTOM_APP_NAME}.app"
ln -s /Applications "$DMG_STAGING/Applications"
hdiutil create -volname "$CUSTOM_APP_NAME" -srcfolder "$DMG_STAGING" -ov -format UDZO "$DMG_OUTPUT"
这样用户打开 DMG 就知道要把 App 拖到 Applications。
体积优化:只保留当前平台的 Codex 二进制
Codex VSIX 很大,因为它包含多平台二进制:macOS arm64、macOS x64、Linux x64、Linux arm64、Windows x64、Windows arm64。我们只打 arm64 的 macOS 包,所以只保留 bin/macos-aarch64,删除其他平台后,DMG 从约 736MB 降到了约 310MB。
正式分发:Developer ID 签名和 Notarization
本地 ad-hoc 签名可以自己打开,但发给同事会出现:
Apple 无法验证"PM Pro"是否包含可能危害 Mac 安全或泄漏隐私的恶意软件。
这是 Gatekeeper 在拦截未公证 App。正式分发需要:
- Developer ID Application 证书
- Hardened Runtime
- Apple notarization
- Staple 公证票据
签名身份是 Developer ID Application: zhixin xie (77TN4427T5)。签名命令核心参数:
codesign --force --deep --options runtime --timestamp \
--entitlements entitlements.plist \
--sign "Developer ID Application: zhixin xie (77TN4427T5)" \
"PM Pro.app"
DMG 也要签,然后提交公证:
xcrun notarytool submit "PM Pro-arm64.dmg" \
--keychain-profile PMProNotary --wait
公证成功后 staple:
xcrun stapler staple "PM Pro-arm64.dmg"
xcrun stapler validate "PM Pro-arm64.dmg"
最终结果:status: Accepted,Gatekeeper 校验通过:PM Pro-arm64.dmg: accepted, source=Notarized Developer ID
坑三:插件里的裸二进制也要单独签名
第一次提交 notarization 返回 Invalid。Apple 日志指出 Codex 插件里的 rg 没有合法 Developer ID 签名:
openai.chatgpt-26.5422.71525/bin/macos-aarch64/rg
The binary is not signed with a valid Developer ID certificate.
codesign --deep 并不总是可靠覆盖扩展目录里的裸二进制。最后在签 App 之前,显式找到扩展里的 macOS 可执行文件逐个签名:
find "$APPDIR/Contents/Resources/app/extensions" \
-type f \
\( -path "*/bin/macos-aarch64/*" -o -path "*/bin/macos-x86_64/*" \) \
-perm -111
然后逐个执行 codesign --force --options runtime --timestamp --entitlements entitlements.plist --sign "$SIGN_IDENTITY" "$binary"。这一步解决了 notarization 的关键问题。
最终效果
最终产物是一个可以正式分发的 macOS arm64 DMG:
PM Pro-arm64.dmg
它具备:
- PM Pro 显示名
- com.xiezhixin.pmpro Bundle ID
- 内置 Codex
- 默认中文
- 默认浅色主题
- 独立用户数据目录
- 标准拖拽安装体验
- Developer ID 签名
- Apple 公证通过并已 staple
这次最大的收获是:做一个"能在自己机器上跑"的 macOS App 很容易,做一个"同事双击也能放心打开"的 macOS App,关键在签名、公证、helper 和默认用户体验这些细节。