Qt 程序写起来爽,发布的时候是真头大。编译调试大半天,好不容易出了 exe,拷到同事电脑上一双击,要么弹“由于找不到 Qt5Core.dll”,要么报一串qt_qpa_platform_plugin_path之类的插件路径错,直接劝退一批人。Qt 官方并没有提供一个“一键出安装包”的傻瓜按钮,而是把部署能力拆散在各种工具里,于是大家就陷入了打包工具的选择困难:windeployqt、linuxdeployqt、CQtDeployer、AppImage、Inno Setup、NSIS、Qt Installer Framework……一个一个看介绍,越看越懵。
这篇内容我尽量用大白话把这摊事讲清楚。我会从 Qt 打包的本质说起,把主流工具的使用场景、优缺点、实测命令和常见坑都过一遍,最后给你一套可以直接照抄的打包方案。不管你是刚接触 Qt 的新手,还是被发布流程折磨过几次的熟手,这篇应该都能帮你省下不少时间。
1. 打包的本质:你到底在打什么包
1.1 Qt 程序不是单个 exe,是一堆依赖的集合
很多新手以为编译通过了,那个 exe 就是全部。实际上 Windows 下一个典型的 Qt Widgets 程序,运行起来至少要这几类东西:
- Qt 自身动态库:比如 Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll,Qt 6 里则是对应的 Qt6Core.dll 等,你用了哪些模块就得带哪些库。
- 平台插件:也就是
platforms/qwindows.dll。没有它就会出现那句经典报错“could not find or load the Qt platform plugin windows”,热搜词里的qt_qpa_platform_plugin_path基本也绕不开它。 - 图像格式插件、样式插件、TLS 插件:
imageformats/下的一堆 dll,tls/下的 qcertonlybackend.dll 等,按需携带。 - 编译器运行库:你用 MSVC 编译就带 vcruntime140.dll、msvcp140.dll,用 MinGW 就带 libgcc_s_seh-1.dll、libstdc++-6.dll、libwinpthread-1.dll。
- QML 相关模块:如果你的界面是 Qt Quick 写的,那还要带上
qml/整个目录里的各种模块,漏一个就报 “module QtQuick is not installed”。 - 第三方依赖:OpenSSL(libssl、libcrypto)、MySQL 客户端库(libmysql.dll)、各种解码库等,这些 windeployqt 大多数不会帮你管,得自己拷。
这么说吧,Qt 程序的发布本质是“依赖收集 + 运行时环境重建”。你在开发机上跑得欢,是因为 Qt 的 bin 目录、编译器目录都在 PATH 或系统环境里;换一台干净的机器,这些关联全断了,自然起不来。
1.2 发布产物有哪几种形态,对应什么需求
打包工具这么多,根源在于产物形态不同,解决的是不同层面的问题:
- 绿色目录版:一个文件夹,里面是 exe + dll + 插件目录,压缩成 zip 发给对方就能跑。最常见,win 下用 windeployqt 生成的就是这种。
- 单文件版:把所有 dll 和资源打成一个 exe,双击直接运行,靠 Enigma Virtual Box 这类工具实现。适合内部工具、临时演示,启动速度和杀软误报率是个隐患。
- 安装包版:exe/msi,向导式安装,带开始菜单、桌面图标、卸载入口,给正式客户交付时基本是标配,Inno Setup、NSIS、Qt Installer Framework 都在干这件事。
- Linux 下的 AppImage/deb/rpm:AppImage 类似绿色版,deb/rpm 类似安装包,不同发行版有不同偏好。
搞清楚自己到底要哪种产物,再选工具,就不会陷入“听起来都很厉害但不知道用哪个”的局面。
1.3 打包工具其实是两类东西
把工具拆开看,就清晰了:一类是依赖收集器,负责把 Qt 的 dll、插件、QML 模块从你的 Qt 安装目录里拉到应用目录,典型就是 windeployqt、linuxdeployqt、macdeployqt、CQtDeployer;另一类是安装包封装器,负责把你整理好的目录变成带界面的安装程序,典型是 Inno Setup、NSIS、Qt Installer Framework、AppImage 工具链。
很多人问“windeployqt 和 Inno Setup 选哪个”,这问题本身就不成立,因为它俩根本不在一个层级。正确姿势是先用 windeployqt 把绿色目录做出来,再用 Inno Setup 把目录包装成安装包。这个关系理清了,后面所有对比都顺了。
2. 主流 Qt 打包工具全景对比
2.1 官方三件套:windeployqt、macdeployqt、linuxdeployqt
官方自带的是 Windows 的 windeployqt 和 macOS 的 macdeployqt。这俩质量最稳,毕竟 Qt 自己维护,对库依赖的解析最准确,命令也简单,在 Qt 的bin目录下直接就有。
windeployqt 的基本用法:
C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe D:\release\MyApp.exe它会扫描 MyApp.exe 的导入表,自动把依赖的 Qt dll、platforms 插件、imageformats、qml 模块等复制到 exe 所在目录。macdeployqt 同理,处理 .app 包的 framework 和插件。
linuxdeployqt 就尴尬一点——它不是 Qt 官方正式维护的。这个项目最早由 probonopd 发起,后来官方收录在 Qt 官方 GitHub 下,但维护节奏一直不稳定,目前处于归档状态,官方推荐用新的 linuxdeploy 配合 qt 插件。不过老项目里 linuxdeployqt 用得还是很多,网上的教程也绝大多数在讲它,所以我们后面 Linux 部分两个方案都会提到。
2.2 社区利器:CQtDeployer、linuxdeploy、AppImage
CQtDeployer 是一个我很看好的跨平台工具,支持 Windows、Linux、macOS,一条命令就能把可执行文件变成可分发的目录:
cqtdeployer -bin MyApp -qmake /path/to/qmake它自动处理 Qt 依赖、插件目录、翻译文件,还支持生成 AppImage、deb、rpm 等格式,算是目前社区里最接近“一站式”的方案。缺点是新东西,资料少,团队小,遇到定制需求得自己啃源码。
linuxdeploy 是 Linux 下的新一代部署工具,配合 qt 插件使用。它不依赖 Qt 安装目录里的部署脚本,而是自己分析依赖,灵活度高:
linuxdeploy -e MyApp --plugin qt --appdir AppDirAppImage 则不是严格意义上的“打包工具”,更准确说是一种打包格式和运行时规范。它把整个应用目录塞进一个 squashfs 镜像,运行时通过 FUSE 挂载跑起来,好处是一个文件到处跑,坏处是某些精简系统没有 FUSE,老版本 AppImage 跑不动,得加--appimage-extract-and-run参数绕过去。
2.3 安装包封装:Inno Setup、NSIS、Qt Installer Framework
依赖收集做完,接下来就是封装层。这里三个主流选手各有脾气:
- Inno Setup:闷声干大事的典范。脚本语法简洁,文档齐全,编译出的安装包小而稳定,支持 lzma2 压缩、卸载器、注册表操作,Windows 下我首选。
- NSIS:老牌,插件生态丰富,但脚本语法比较古董,写复杂逻辑容易头大。如果你的安装逻辑很特殊(比如自定义页面、网络下载组件),NSIS 的可扩展性更强。
- Qt Installer Framework(QtIFW):Qt 亲儿子,主打组件化安装和在线/离线更新。适合大型项目、需要增量更新、需要按模块勾选安装的场景。缺点是学习曲线陡,脚本基于 XML,官方文档写得晦涩,第一次配置容易劝退。
还有一个 Enigma Virtual Box,它不做安装包,而是把程序和 dll 虚拟化打进单文件 exe。内部小工具发布很方便,但正式产品我不推荐,杀软误报和启动时的解压损耗都是问题。
2.4 怎么选:先看发布目标,再定工具链
我列一个决策速查表,你照着对号入座就行:
| 场景 | 首选方案 | 备选方案 |
|---|---|---|
| Windows 内部工具,zip 发给同事 | windeployqt + 手动整理 | CQtDeployer |
| Windows 正式发布,给客户安装 | windeployqt + Inno Setup | windeployqt + NSIS |
| Windows 大型软件,需要组件化安装和在线升级 | windeployqt + QtIFW | windeployqt + NSIS + 自研更新器 |
| Linux 通用桌面发布 | linuxdeploy + AppImage | linuxdeployqt + AppImage |
| Linux 发行版原生包 | 打 deb/rpm,依赖交给包管理器 | linuxdeploy + deb 插件 |
| 跨平台项目,想统一工具链 | CQtDeployer | 各平台官方 deploy 工具 |
| PySide6/PyQt6 项目 | PyInstaller + windeployqt 二次处理 | Nuitka + windeployqt |
核心思路就一句话:依赖收集用官方或社区部署工具,安装封装再根据平台选安装器,两层分工,不要混着比。
3. Windows 下完整打包实战
3.1 用 windeployqt 做出可运行的绿色目录
我在 Qt 5.15.2 MSVC2019 环境下的标准操作,全程命令行,干净利落:
mkdir D:\deploy\MyApp copy /y D:\build\release\MyApp.exe D:\deploy\MyApp\ cd /d D:\deploy\MyApp C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe MyApp.exe --no-translations注意几点:
- 不传
--debug就默认 release 模式,别 debug 和 release 混用,否则一堆带 d 后缀的调试库打进去,运行还要 VC debug 运行库,纯属自找麻烦。 --no-translations的意思是不要 Qt 自带的语言翻译文件,如果你界面是中文且没加载qt_zh_CN.qm,加上这个能省 20 多 MB。- 默认参数会把 MSVC 运行库 vcruntime140.dll、msvcp140.dll 一起拷进来。想在目标机器上走“安装 VC 运行库”这条路,可以加
--no-system-dll,但新手我不建议这么干,很容易漏。还是老老实实默认全带,体积大点不影响正确性。
跑完看一眼目录,正常应该长这样:
MyApp.exe Qt5Core.dll Qt5Gui.dll Qt5Widgets.dll platforms\ qwindows.dll styles\ qmodernwindowsstyle.dll imageformats\ qjpeg.dll qgif.dll ...如果用了 Qt Quick,还会多出qml\目录。这一步做完,理论上去一台没装 Qt 的机器就能跑了。
3.2 手动补齐第三方库和运行时
windeployqt 管不了第三方依赖,这一步得自己上。
我自己遇到过最典型的是 OpenSSL。用 Qt5Network 访问 HTTPS 接口,程序在开发机上没事,发布后一请求就报 TLS 初始化失败。原因就是没带libcrypto-1_1-x64.dll和libssl-1_1-x64.dll。解决方式:从本机 OpenSSL 安装目录或者 Qt 自带的tools/OpenSSL目录把这两个 dll 拷到 exe 目录。
数据库驱动同理,MySQL 的libmysql.dll拷到 exe 目录,同时确认 Qt 的plugins/sqldrivers/qsqlmysql.dll在绿色目录里。Qt 5.12 之后的 MySQL 驱动还要匹配 MySQL 8 的认证插件,客户端库版本别太老。
还有一个高频的坑:程序用到了 WebEngine 或高级 OpenGL 功能,目标机器提示缺少d3dcompiler_47.dll。这个文件在 Windows 10 1903 之后系统自带,但 Win7/老版本 Win10 上经常缺失。稳妥做法是把C:\Windows\System32\d3dcompiler_47.dll直接拷到应用目录。这类“系统库缺失”基本都靠这一步兜底。
另外强烈建议在 exe 旁边放一个qt.conf:
[Paths] Prefix=. Plugins=plugins Imports=imports Qml2Imports=qml Translations=translations别看它简单,作用很大:告诉 Qt 以 exe 所在目录为基准去找插件和 QML 模块。没有它,程序在某些环境下可能因为当前工作目录不对,又跑回“找不到平台插件”的老路。
3.3 用 Inno Setup 把绿色目录变成安装包
绿色目录自测没问题后,下一步封装安装包。我用的 Inno Setup 脚本长这样:
[Setup] AppName=MyApp AppVersion=1.0.0 DefaultDirName={autopf}\MyApp OutputDir=installer OutputBaseFilename=MyAppSetup Compression=lzma2 SolidCompression=yes ArchitecturesInstallIn64BitMode=x64compatible [Files] Source: "D:\deploy\MyApp\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs [Icons] Name: "{autoprograms}\MyApp"; Filename: "{app}\MyApp.exe" Name: "{autodesktop}\MyApp"; Filename: "{app}\MyApp.exe"; Tasks: desktopicon [Tasks] Name: "desktopicon"; Description: "Create desktop icon"; GroupDescription: "Additional icons:"几个要点:
Source里的recursesubdirs必须写,它会带着 platforms、imageformats、qml 这些子目录一起进安装包。漏了这个标志,装到用户机器上照样缺插件报错。ArchitecturesInstallIn64BitMode=x64compatible的意思是让 64 位程序默认装到 Program Files。注意老版本 Inno Setup 不认识x64compatible,用x64也可以,但新版推荐前者。- 如果应用还带了自己生成的数据文件、日志目录,建议把程序目录和用户数据目录分开,不要都塞
{app}下。
编译完生成MyAppSetup.exe,找个干净虚拟机装一遍,验证卸载和图标是否正常。
3.4 发布前的自检清单
我在 Windows 上做最终检查,按下面这几条过一遍:
- 一台干净的不带 Qt 的机器(虚拟机就行)上跑绿色目录版,双击 exe,确认能启动。
- 运行
QT_DEBUG_PLUGINS=1 MyApp.exe,观察控制台输出。如果哪个插件加载失败,会清清楚楚列出来,比瞎猜快得多。 - 检查
platforms\qwindows.dll存在,检查qml\目录(用 Qt Quick 的话)存在,检查第三方 dll 都在根目录。 - 用 Process Explorer 打开运行中的进程,查看加载的 dll 列表,对比开发机上缺哪些。这一步基本能定位所有“暗缺依赖”。
4. Linux 下打包实战:从 Ubuntu 到麒麟
4.1 老牌方案 linuxdeployqt 的实操和局限
Linux 下的依赖收集比 Windows 麻烦,因为动态库的依赖链更深,还要关心 glibc 版本、xcb 插件依赖的系统库。老方案 linuxdeployqt 的用法:
wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage chmod +x linuxdeployqt-continuous-x86_64.AppImage export PATH=/opt/Qt/5.15.2/gcc_64/bin:$PATH mkdir -p AppDir/usr/bin cp MyApp AppDir/usr/bin/ ./linuxdeployqt-continuous-x86_64.AppImage AppDir/usr/bin/MyApp -appimage它会把 Qt 的 .so 收集到AppDir/usr/lib,并把插件放好,最后生成 AppImage。这里有几个坑:
- 必须提前设置 PATH,让工具能找到 qmake。找不到 qmake 时它会报 “Could not find qmake” 然后罢工。
- AppDir 目录结构有约定,可执行文件要放在
usr/bin下,不要直接扔 AppDir 根目录。 - linuxdeployqt 已经归档,在新系统上跑可能出现 Python 脚本兼容问题,这是旧方案最大的隐患。
4.2 更推荐的新方案 linuxdeploy + qt 插件
现在社区主流推荐 linuxdeploy,它是模块化设计,Qt 支持通过插件实现:
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-qt-x86_64.AppImage export QMAKE=/opt/Qt/5.15.2/gcc_64/bin/qmake mkdir -p AppDir/usr/bin cp MyApp AppDir/usr/bin/ ./linuxdeploy-x86_64.AppImage -e MyApp --plugin qt --appdir AppDirlinuxdeploy 会自己扫描 Qt 插件和依赖,不太依赖你安装 Qt 时的部署文件。最后再:
./linuxdeploy-x86_64.AppImage --appdir AppDir --output appimage就能拿到 AppImage。注意--plugin qt必须在安装 Qt 插件的情况下才有效,这个插件是通过环境变量QMAKE找到 Qt 库路径的。
4.3 AppImage 跑不起来的常见原因
AppImage 在不同发行版之间跑不起来,多半是这几个原因:
- 目标机器没装 FUSE。新版 AppImage 需要 fuse3,老系统只有 fuse2。可以在启动时加
--appimage-extract-and-run,但把这个问题抛给用户显然不专业。 - glibc 版本过低。你在 Ubuntu 22.04 上打的包,拿到 CentOS 7 上往往跑不起来,原因是 glibc 版本低于构建机。解决方案是找一台最低版本的目标系统做构建机,或者用 Docker 拉一个旧版 base image 来打包。
- 字体、中文输入法缺失。Qt 程序在精简 Linux 上容易显示方块字,需要带字体或者在系统里安装
fonts-wqy-zenhei等中文字体,打包时把字体文件放进 AppDir 并设置QTPATH相关环境变量也很常见。
4.4 麒麟 x86 系统离线部署思路
国产麒麟系统(x86 架构)本质是 Linux 发行版,Qt 打包逻辑和 Ubuntu 大同小异,但有几个实际区别:
- 很多内网环境不能上网,你开发的 Qt 是离线安装的,目标机器上也没有包管理器源。此时最好的方案不是丢一个 AppImage,而是做一个 deb 包,把依赖写好,离线安装时用
dpkg -i配上本地依赖包一起装。 - 麒麟的 xcb 相关系统库不一定装全,常见缺
libxcb-xinerama0、libxkbcommon-x11-0。在你自己的打包机上先ldd MyApp扫一遍,缺什么就在构建脚本里写清楚,或者打进安装包。 - 如果目标机器是飞腾/鲲鹏的 ARM 架构,那就必须用 ARM 版 Qt 交叉编译或者直接在 ARM 机器上编译,x86 的产物跑不了,这个和工具选型无关,纯架构约束。
我的建议是:麒麟 x86 环境优先做 deb 包,用 fpm 或者手动打,把libqt5core5a、libqt5gui5等写明依赖,内网用本地源装。图省事就 ld 脚本收集依赖做一个绿色目录,直接拷贝运行,但要接受不同机器系统库版本不一致的隐患。
5. Qt 打包经典报错速查表
5.1 高频报错一览
我整理了平时遇到最多、群里问得最多的几个,直接对照处理:
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
| could not find or load the Qt platform plugin windows | platforms/qwindows.dll 缺失或路径不对 | 用 windeployqt 重新部署,或写 qt.conf 指定 Plugins 路径 |
| 提示 qt_qpa_platform_plugin_path 相关的路径错 | 插件目录没放到预期位置,环境变量被设置成开发机路径 | 检查是否自定义了 QT_QPA_PLATFORM_PLUGIN_PATH,清除或改为相对路径 |
| 由于找不到 Qt5Core.dll | Qt 库没带全 | windeployqt 部署后确认 Qt5Core.dll 在 exe 目录 |
| 缺少 VCRUNTIME140.dll 或 msvcp140.dll | VC 运行库缺失 | 打包时默认带编译器运行库,或安装 vc_redist.x64.exe |
| 缺少 d3dcompiler_47.dll | 老系统缺 DirectX 相关库 | 从 System32 拷一份到应用目录 |
| module "QtQuick" version 2.15 is not installed | QML 模块没带全 | 把整个 qml 目录拷到应用目录,用 qt.conf 指到 Qml2Imports |
| QMYSQL driver not loaded | 数据库驱动插件事务缺失或 libmysql 缺失 | 检查 plugins/sqldrivers/qsqlmysql.dll,拷贝 libmysql.dll |
| 启动闪退,无任何提示 | 第三方 dll 缺失或插件加载失败 | 用 QT_DEBUG_PLUGINS=1 看插件日志,用 ldd 查 Linux 依赖 |
| 编译时报 dependent '......\allinstall\qt\5.15.2\msvc2019\include\qtw...' | .pro 或 CMake 写死了开发机绝对路径,Qt 安装路径漂移 | 清理构建缓存,改用 $$[QT_INSTALL_HEADERS] 等 Qt 内置变量 |
| AppImage 在别的机器上打不开 | FUSE 缺失或 glibc 版本不匹配 | 加 --appimage-extract-and-run 应急,长期方案是低版本系统构建 |
5.2 排查依赖的实用工具和方法
遇到诡异问题,先别急着重新打包,按这个排查顺序来:
- Windows 下用 Process Explorer 打开进程,右键“Properties”查看“Image”列表,能看到所有加载的 dll。对照开发机的加载列表,缺哪个一目了然。
- Linux 下用
ldd MyApp看动态库依赖,输出里出现 “not found” 就是缺系统库。再用readelf -d MyApp | grep NEEDED看更深层依赖。 - 万能环境变量
QT_DEBUG_PLUGINS=1,Windows 和 Linux 都支持,启动程序后控制台会打印每个插件加载路径和结果,几乎所有“平台插件”类问题都能在这里看到答案。 - 自己写个脚本遍历 release 目录,把文件清单导出来,用
QDir::entryInfoList递归收集目录内容是可以的,但更省事的方式是用dir /s /b或者在工程里加一个小工具函数,把依赖文件列表输出成文本,方便比对。
排查的错误见多了就会发现,90% 的打包问题根源只有三个:插件路径不对、依赖库缺失、版本不匹配。定位到具体是哪个,解决起来其实都很快。
6. 我的一点个人经验
踩过几次坑之后,我的习惯已经固化了:Windows 产品发布固定用 windeployqt 做依赖收集,再用 Inno Setup 包一层,脚本写进 CI,每次构建产物直接出安装包,再也不手工拷 dll。Linux 那边我用 linuxdeploy 打 AppImage 应对“到处跑”的需求,给麒麟系统交付时单独打 deb 包。两者互不冲突,都在一条流水线里。
最后再分享一个小技巧:发布前别光在自己 64 位 Windows 11 上测试,准备一台 Win10 的干净虚拟机,跑一遍绿色版和安装包。很多你以为“不可能缺”的 dll,在人家机器上就是缺了。打包工具再智能,也不如一次真实环境的验证来得可靠。