3、macOS平台部署:应用捆绑包(.app)结构、macdeployqt工具、代码签名与公证、DMG制作与分发

macOS 的部署,说实话,跟 Windows 和 Linux 完全是两码事。我第一次在 Mac 上打包 Qt 应用时,就被那个 .app 文件夹搞懵了——它看起来像个文件,双击却能运行,里面还藏着一整套目录结构。后来我才明白,这就是 macOS 的「应用捆绑包」机制。

嗯,咱们一步步来。先搞清楚 .app 里面到底长什么样,再谈怎么用工具自动搞定它。

3.1 应用捆绑包(.app)结构

macOS 的应用,本质上是一个文件夹。只不过系统给它穿了个马甲,让你看起来像个单一文件。你可以在 Finder 里右键点击任何 .app,选择「显示包内容」,就能看到它的真面目。

一个典型的 Qt 应用捆绑包结构是这样的:

MyApp.app/
├── Contents/
│   ├── Info.plist          # 应用的元信息配置文件
│   ├── MacOS/
│   │   └── MyApp           # 真正的可执行文件
│   ├── Resources/
│   │   ├── icon.icns       # 应用图标
│   │   ├── MainMenu.nib    # 界面资源(如果用了)
│   │   └── *.qm            # 翻译文件
│   └── Frameworks/         # 依赖的框架(Qt 库等)
│       ├── QtCore.framework
│       ├── QtGui.framework
│       └── ...

这里面最关键的,是 Info.plist。它就像应用的身份证,告诉系统你的应用叫什么、用什么图标、支持哪些文件类型。我见过不少新手直接复制别人的 plist 文件,结果应用图标死活不显示——其实就是 CFBundleIconFile 字段写错了。

Info.plist 必填字段:

  • CFBundleName:应用显示名称
  • CFBundleDisplayName:用户看到的名称
  • CFBundleIdentifier:唯一标识符,比如 com.mycompany.myapp
  • CFBundleVersion:版本号
  • CFBundleExecutable:可执行文件名
  • CFBundleIconFile:图标文件名(不带 .icns 后缀)

我个人习惯把 Info.plist 放在项目的 resources/ 目录下,然后在 .pro 文件里用 QMAKE_INFO_PLIST 指定路径。这样每次编译都会自动复制进去,省心。

3.2 macdeployqt 工具

手动往 .app 里塞 Qt 框架?别傻了。Qt 官方提供了 macdeployqt,一条命令就能搞定依赖拷贝。

基本用法:

macdeployqt /path/to/MyApp.app -verbose=1

它会自动做三件事:

  • 扫描可执行文件,找出所有依赖的 Qt 框架
  • 把这些框架拷贝到 Contents/Frameworks/ 下
  • 用 install_name_tool 修改可执行文件的库搜索路径,让它们指向包内的框架

我曾经在项目里用了一个第三方库,macdeployqt 死活没把它打包进去。后来发现,那个库是用 dlopen 动态加载的,macdeployqt 扫描不到。解决办法是手动指定:

macdeployqt MyApp.app -executable=MyApp.app/Contents/MacOS/MyApp \
  -libpath=/usr/local/lib

小技巧:如果你用了 QML,记得加 -qmldir=/path/to/qml 参数。否则 QML 插件和导入库不会被自动打包,应用在别的机器上会白屏。

还有一个坑:macdeployqt 默认不会处理 .dylib 文件。如果你自己编译了动态库,得手动把它们扔进 Frameworks/ 目录,或者用 -executable 参数逐个指定。

3.3 代码签名与公证

从 macOS 10.15 Catalina 开始,苹果强制要求所有应用必须经过「公证」(Notarization)才能正常运行。说白了,就是你的应用得先让苹果服务器扫描一遍,确认没病毒,然后用户才能打开。

签名和公证的流程,我总结成三步:

  1. 签名你的应用:用 codesign 命令给 .app 打上你的开发者证书
  2. 提交公证:用 xcrun altool 或 notarytool 把应用上传给苹果
  3. 绑定公证票据:把苹果返回的「通过」凭证嵌入到 .app 里

签名命令长这样:

codesign --deep --force --verify --verbose \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  --options runtime \
  MyApp.app

注意那个 --options runtime,这是硬性要求。不加的话,公证会失败。我当初就因为这个被拒了三次,气得想砸电脑。

公证提交(新版 notarytool):

xcrun notarytool submit MyApp.app \
  --apple-id your@email.com \
  --team-id TEAMID \
  --password @keychain:AC_PASSWORD \
  --wait

警告:公证需要联网,而且苹果的审核队列有时候很慢。我遇到过等 20 分钟才出结果的。建议在 CI/CD 流程里预留足够的时间。

公证通过后,别忘了绑定票据:

xcrun stapler staple MyApp.app

这一步是把苹果的「已公证」标记写入 .app,这样用户第一次打开时就不会弹警告了。

3.4 DMG 制作与分发

应用打包好了,怎么发给用户?macOS 上最通用的方式就是 DMG 镜像。它就像一个压缩包,但打开后会自动弹出窗口,用户把 .app 拖进 Applications 文件夹就行。

制作 DMG 我推荐用 create-dmg 这个开源工具。它比手动用 hdiutil 方便太多了:

create-dmg \
  --volname "MyApp" \
  --volicon "icon.icns" \
  --background "background.png" \
  --window-pos 200 120 \
  --window-size 800 400 \
  --icon-size 100 \
  --icon "MyApp.app" 200 190 \
  --hide-extension "MyApp.app" \
  --app-drop-link 600 185 \
  "MyApp-1.0.dmg" \
  "MyApp.app"

参数解释一下:

  • --volname:DMG 挂载后的卷标名
  • --background:背景图,建议 800x400 像素
  • --icon:指定 .app 图标在窗口里的位置
  • --app-drop-link:那个指向 Applications 文件夹的快捷箭头位置

我个人习惯在 DMG 里放一个 README.txt,写上版本号和更新日志。用户打开 DMG 就能看到,不用额外打开网页。

DMG 制作要点:

  • DMG 本身也要签名和公证!否则用户下载后可能打不开
  • 背景图不要用太复杂的图案,简洁为主
  • 窗口大小和图标位置要提前算好,别让图标叠在一起
  • 建议用 --no-internet-enable 参数,避免 DMG 联网检查

最后,别忘了给 DMG 签名:

codesign --sign "Developer ID Application: Your Name (TEAMID)" \
  MyApp-1.0.dmg

然后再公证一次。嗯,你没看错,DMG 也要公证。苹果就是这么「严谨」。

知识体系总览

下面这张图,是我自己总结的 macOS 部署全流程。你可以把它当成 checklist,每次打包时对照着走一遍:

macOS 部署全流程 1. 构建应用 编译 Qt 项目 生成 .app 捆绑包 2. 部署依赖 macdeployqt 拷贝 Qt 框架 3. 代码签名 codesign 签名 --options runtime 4. 公证 notarytool 提交 stapler 绑定票据 5. 制作 DMG create-dmg 工具 设置背景/图标 6. 签名 DMG codesign 签名 再次公证 7. 分发 上传到官网 / App Store / 第三方分发平台 注意:步骤 3-6 在 macOS 10.15+ 上是强制要求,缺一不可

说实话,macOS 的部署流程比 Windows 复杂不少。但只要你把签名和公证的脚本写好了,后面就是一条命令的事。我个人习惯把这整套流程写成一个 deploy_mac.sh 脚本,放在项目根目录下。每次发版时跑一遍,省时省力。

嗯,关于 macOS 部署,核心就是这些。记住:.app 结构要完整,依赖要打包干净,签名和公证不能跳过,DMG 要做得漂亮。做到这四点,你的 Qt 应用在 Mac 上就能跑得稳稳当当。