30、CPack实战案例二:构建一个带Qt界面的桌面应用并打包为NSIS安装包和DMG镜像
说实话,Qt 桌面应用打包这件事,我踩过的坑比写代码的时间还多。早些年我还在用 Qt4 的时候,给客户发一个 exe 过去,对方打开就报“缺少 DLL”,那场面别提多尴尬了。后来我学乖了,老老实实用 CPack 配合 NSIS 和 DMG 来做安装包。今天我就把这个流程完整走一遍,你跟着做就行。
30.1 项目结构设计
我个人习惯把 Qt 项目拆成三层:源码、资源、打包脚本。这样后期维护起来特别清爽。来看一个典型的目录结构:
MyQtApp/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ ├── mainwindow.h
│ └── mainwindow.cpp
├── resources/
│ ├── app.qrc
│ └── icons/
│ └── app.ico
└── cmake/
└── packaging.cmake
嗯,这里要注意:packaging.cmake 文件我单独拎出来了,因为打包逻辑和构建逻辑混在一起的话,后面改起来很痛苦。你想想看,如果哪天要换安装包格式,你只需要改这一个文件就行。
30.2 核心 CMakeLists.txt 配置
先看主 CMakeLists.txt。这里我用了 find_package 来定位 Qt,然后设置 CMAKE_INSTALL_PREFIX。这个前缀路径决定了安装包里的目录结构。
cmake_minimum_required(VERSION 3.16)
project(MyQtApp VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 找 Qt
find_package(Qt5 COMPONENTS Widgets REQUIRED)
# 源文件
set(SOURCES
src/main.cpp
src/mainwindow.cpp
src/mainwindow.h
)
# 资源文件
set(RESOURCES
resources/app.qrc
)
# 生成可执行文件
add_executable(${PROJECT_NAME} ${SOURCES} ${RESOURCES})
# 链接 Qt
target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets)
# 设置安装规则
install(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin
)
# 引入打包配置
include(cmake/packaging.cmake)
这里有个小细节:RUNTIME DESTINATION bin 是给 Windows 用的,Linux 和 macOS 下会忽略。我刚开始做跨平台打包时,总以为要写三个不同的 install 规则,后来发现 CMake 自己会处理。
30.3 打包配置:packaging.cmake
这个文件是核心。我把它拆成两部分:通用设置和平台特定设置。
30.3.1 通用设置
# 设置 CPack 基本信息
set(CPACK_PACKAGE_NAME "MyQtApp")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A Qt Desktop Application")
set(CPACK_PACKAGE_CONTACT "support@mycompany.com")
# 设置输出目录
set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_BINARY_DIR}/packages")
30.3.2 Windows NSIS 配置
NSIS 打包,说白了就是生成一个 exe 安装向导。我最头疼的是 Qt 的 DLL 依赖,少一个就崩。后来我用了 windeployqt 工具来自动收集 DLL。
if(WIN32)
# 启用 NSIS 生成器
set(CPACK_GENERATOR "NSIS")
# 安装后自动运行 windeployqt
install(CODE "
execute_process(
COMMAND windeployqt --release
\"${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}.exe\"
WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}/bin\"
)
")
# NSIS 特定设置
set(CPACK_NSIS_DISPLAY_NAME "MyQtApp")
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
set(CPACK_NSIS_CREATE_ICONS_EXTRA
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\MyQtApp.lnk' '$INSTDIR\\\\bin\\\\${PROJECT_NAME}.exe'"
)
set(CPACK_NSIS_DELETE_ICONS_EXTRA
"Delete '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\MyQtApp.lnk'"
)
endif()
注意: 我曾经在 NSIS 打包时忘记设置 CPACK_NSIS_INSTALL_ROOT,结果安装包默认装到了 32 位目录下。64 位应用一定要指定 $PROGRAMFILES64。
30.3.3 macOS DMG 配置
macOS 打包相对简单,但有个坑:DMG 镜像的卷标不能有中文,否则挂载时会乱码。我吃过这个亏,后来老老实实用英文卷标。
if(APPLE)
# 启用 DMG 生成器
set(CPACK_GENERATOR "DragNDrop")
# 安装后自动运行 macdeployqt
install(CODE "
execute_process(
COMMAND macdeployqt
\"${CMAKE_INSTALL_PREFIX}/bin/${PROJECT_NAME}.app\"
WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}/bin\"
)
")
# DMG 特定设置
set(CPACK_DMG_VOLUME_NAME "MyQtApp")
set(CPACK_DMG_FORMAT "UDBZ")
set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/resources/DS_Store")
endif()
小技巧: CPACK_DMG_DS_STORE 可以指定一个预制的 .DS_Store 文件,这样生成的 DMG 打开后窗口大小和图标位置都是你想要的。我一般先在 Finder 里手动调整好,然后把隐藏的 .DS_Store 复制出来。
30.4 构建与打包命令
构建命令其实很简单,但顺序不能乱。我习惯用 cmake --build 而不是直接 make,因为这样更通用。
# 配置
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=./install
# 构建
cmake --build . --config Release
# 安装到临时目录
cmake --install . --config Release
# 打包
cpack --config CPackConfig.cmake
打包完成后,在 build/packages 目录下会生成 MyQtApp-1.0.0-win64.exe 或 MyQtApp-1.0.0-Darwin.dmg。
30.5 核心流程 SVG 图
下面这张图展示了从源码到安装包的完整流程。我特意把 windeployqt 和 macdeployqt 标红了,因为这是最容易出问题的环节。
30.6 避坑指南
我总结几个实战中容易翻车的地方:
- Qt 版本不匹配: 我曾经用 Qt 5.15 编译,但系统里装的是 Qt 5.12 的 windeployqt,结果打出来的包缺了
Qt5Core.dll。一定要确保windeployqt和编译用的 Qt 版本一致。 - NSIS 中文乱码: 安装向导里的中文文本如果编码不对,会显示成乱码。我建议在
CPackConfig.cmake里显式设置CPACK_NSIS_ENCODING UTF-8。 - DMG 卷标长度: macOS 的 DMG 卷标不能超过 27 个字符,否则挂载时会截断。我一般控制在 20 个字符以内。
- 权限问题: macOS 下如果
macdeployqt报权限错误,记得给可执行文件加上执行权限:chmod +x。
核心要点: Qt 桌面应用打包的关键在于依赖收集。无论是 Windows 的 DLL 还是 macOS 的 Framework,少一个应用就起不来。CPack 本身不处理这些,你需要借助 windeployqt 或 macdeployqt 来完成。我建议在 CI/CD 流程中加上这一步的自动化验证,比如打包后启动应用看看能不能正常显示窗口。
30.7 验证打包结果
打包完成后,我习惯做三件事来验证:
- 检查文件完整性: 用
dumpbin /dependents(Windows)或otool -L(macOS)查看可执行文件的依赖是否都在。 - 在干净环境安装: 找一台没有 Qt 的机器或虚拟机,安装后运行,看是否报错。
- 检查安装路径: 确保安装目录没有硬编码的绝对路径,否则换台机器就崩了。
嗯,基本上做到这三点,你的安装包就稳了。我当年第一次用 CPack 打包 Qt 应用时,就是因为没做第二步验证,结果发给客户后对方说打不开,排查了半天发现是少了 platforms/qwindows.dll。从那以后,我每次打包完都会在干净环境里跑一遍。
公众号:蓝海资料掘金营,微信deep3321