简介:面向需要在Ubuntu下将Qt程序打包并分发至无Qt环境主机的开发者,这份PDF文档围绕linuxdeployqt工具,系统讲解从Qt环境配置、编译linuxdeployqt源码到最终打包运行的完整流程。文档以实操方式给出~/.bashrc中PATH、LD_LIBRARY_PATH、QT_PLUGIN_PATH、QML2_IMPORT_PATH等环境变量的设置方法,并针对Ubuntu 18.04等高版本系统编译源码时需注释glibc版本检查的细节做了说明。打包环节重点分析了两个高频错误:patchelf工具缺失导致rpath读取失败,以及libjasper.so.1缺失导致图片格式插件加载异常,均给出了直接的apt安装命令和排错思路;读者还可参考其中利用ldd定位缺失依赖、手动补充Qt字体、图标与QML模块的方法。资源为单个PDF文档,大小约58KB,篇幅精炼但覆盖关键难点,已有2720人学习,适合需要快速掌握linuxdeployqt打包流程的C++/Qt开发者参考。
1. 写在前面:linuxdeployqt这把双刃剑
在Ubuntu下给Qt程序打包,我猜你十有八九跟linuxdeployqt较过劲。这东西本来是给Linux桌面应用做AppImage打包的,但实际用起来,报错能让人怀疑人生——平台插件找不到、xcb依赖缺失、ICU版本不对、qmake路径踩错……我最早接触它是在把一个Qt 5.12的串口工具发布给客户时,折腾了整整两天,最后总算摸透了它的脾气。
这篇就专注解决一个问题:在Ubuntu上,怎么把别人机器上编译好的Qt程序,用linuxdeployqt打包成一扔就能跑的独立可执行文件。适合刚上手Linux Qt开发、或者正被各种缺库报错折磨的兄弟参考。我会把原理、流程、常见坑一次讲清,尽量让你少走弯路。
先说结论:linuxdeployqt本身不复杂,复杂的是一堆环境依赖和Qt安装方式的坑,而这些坑大多数时候是可以绕开的。
2. 打包前必须搞懂的三件事
2.1 linuxdeployqt到底在做什么
简单来说,linuxdeployqt做的事就两件:一是把可执行文件依赖的Qt库(比如libQt5Core.so.5、libQt5Gui.so.5)复制到目标目录里,二是去改写可执行文件里的库搜索路径,让它在运行时能从相对路径找到这些库,而不是只依赖系统的标准路径。
它底层靠的是ldd扫描依赖、patchelf修改ELF的RPATH或者RUNPATH。对Qt程序来说,它还会根据你源码里用过的模块去补全插件,比如platforms/目录下的libqxcb.so,这个特别关键——如果你遗漏了这个插件,就会出现经典的could not find or load the Qt platform plugin "xcb"报错。
理解这一点很重要,因为很多排查思路都建立在这个机制上。它不是万能药,只解决Qt自身依赖的问题。如果你的程序还依赖了一些别的第三方库(比如OpenCV、FFmpeg),linuxdeployqt默认不会帮你带,除非你用额外的参数去处理。
2.2 为什么选linuxdeployqt而不是其他方案
打包Linux Qt程序主要有三条路:一是直接编译成静态链接的Qt版本,但Qt官方对静态链接的授权有限制(开源版必须注意动态链接的合规性),而且很多插件做静态化很麻烦;二是用AppImage官方推荐的linuxdeployqt工具链,也就是这里要说的方案;三是自己手写shell脚本复制库+修RPATH,能行但极其费劲。
我自己的体会是:linuxdeployqt最大的优势是自动化程度高,配合-appimage参数一条命令能出产物;缺点是它对新版Qt和部分桌面环境的兼容性不够完美,一不注意就踩坑。如果你只是要一个能跑的目录(非AppImage单文件),用它就够了;如果你非要生成AppImage,那还得额外装linuxdeploy这个辅助工具。
2.3 打包环境的准备细节
建议在一台比较干净的Ubuntu 18.04/20.04/22.04上做打包。为什么强调"干净"?因为如果系统里已经装了一堆乱七八糟的Qt版本、各种LD_LIBRARY_PATH,linuxdeployqt扫描到的Qt库路径可能不是你编译时用的那套,最后打包出来的程序在其他机器上跑不起来。
准备阶段你得先把这几个东西装好:
sudo apt update sudo apt install build-essential libgl1-mesa-dev patchutils sudo apt install qt5-default # 或者你需要的Qt版本然后下载linuxdeployqt,注意要选对版本。官方GitHub的Release页面提供了linuxdeployqt-continuous-x86_64.AppImage这种持续构建版,直接下载加执行权限就行:
wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage chmod +x linuxdeployqt-continuous-x86_64.AppImage下载完先确认它能跑:./linuxdeployqt-continuous-x86_64.AppImage --version。如果缺FUSE跑不起来,用--appimage-extract-and-run参数绕过去。这一步很多人会卡住,记住了。
3. 核心细节:库扫描、qmake路径与插件复制
3.1 qmake路径是第一个坑
linuxdeployqt运行时会去调用qmake来获取Qt的安装路径、模块列表等信息。如果你的系统里有多个Qt(比如Qt Creator自带的、系统装的、手动编译的),linuxdeployqt很可能找错。
常见报错是:Could not find qmake in PATH,或者打包出来到了别的机器上报Qt version mismatch。
解决思路很明确:把编译这个程序时用的那个qmake所在目录放到PATH最前面。比如你用的Qt 5.15.2装在/opt/Qt/5.15.2/gcc_64/bin,那就:
export PATH=/opt/Qt/5.15.2/gcc_64/bin:$PATH export LD_LIBRARY_PATH=/opt/Qt/5.15.2/gcc_64/lib:$LD_LIBRARY_PATH然后先跑qmake -v确认版本,再跑linuxdeployqt。这一步能解决大概一半的诡异问题。
3.2 库扫描的坑与对策
linuxdeployqt靠ldd扫描。但ldd有个特性:它会优先读取当前环境里的LD_LIBRARY_PATH。如果你在打包时设了乱七八糟的库路径,它扫到的依赖可能不是你预期的那一份。
我的习惯是:打包前清空LD_LIBRARY_PATH(临时unset掉),只保留PATH里的qmake指向。这样扫描结果最干净。
另外一个注意点:如果你的程序里通过dlopen动态加载了某些库(比如插件系统),linuxdeployqt默认扫不到。应对办法是手动把那些动态加载的so复制到打包目录的相应位置,或者用unsupported-ignore-errors参数配合白名单去补。
3.3 平台插件的完整复制逻辑
linuxdeployqt会把Qt的插件目录整个复制到打包目录下的plugins文件夹。它是通过读取Qt安装目录里的qt_plugins路径来实现的。如果你用的是自己编译的Qt,插件路径很可能不在标准位置,导致复制不完整。
这时你可以用-qmldir参数指定QML模块目录(如果你的程序用了QML),或者直接手动复制插件:
cp -r /opt/Qt/5.15.2/gcc_64/plugins/platforms ./打包目录/ cp -r /opt/Qt/5.15.2/gcc_64/plugins/imageformats ./打包目录/ # 其他用到的插件按需补充经验之谈:platforms/libqxcb.so是重中之重,缺了它几乎一定会报platform plugin "xcb"错误。如果出现这个报错,优先检查打包目录下的plugins/platforms/里有没有这个文件。
4. 实操过程:一条完整的打包链路
4.1 准备项目与编译
咱们用一个最简单的Qt Widgets程序来演示。假设项目目录为/home/user/MyApp,里面有MyApp.pro、main.cpp、mainwindow.cpp等文件。
先确保用正确的qmake编译:
cd /home/user/MyApp mkdir build && cd build qmake ../MyApp.pro make -j4编译成功后你会得到可执行文件MyApp。用ldd MyApp看一下它的依赖,这里面会列出系统路径里的Qt库、X11库、xcb库等。这些信息后面排错很有用。
4.2 用linuxdeployqt生成可分发目录
先把可执行文件复制到一个干净的发布目录中,比如/home/user/MyApp/release:
mkdir -p /home/user/MyApp/release cp /home/user/MyApp/build/MyApp /home/user/MyApp/release/ cd /home/user/MyApp/release然后执行打包命令:
export PATH=/opt/Qt/5.15.2/gcc_64/bin:$PATH unset LD_LIBRARY_PATH ../linuxdeployqt-continuous-x86_64.AppImage ./MyApp -verbose=2这里的-verbose=2很推荐,它能打印详细的拷贝日志,方便你看到底复制了哪些库、漏了哪些。
命令跑完后,目录下应该是这样的结构:
release/ ├── MyApp ├── lib/ │ ├── libQt5Core.so.5 │ ├── libQt5Gui.so.5 │ ├── libQt5Widgets.so.5 │ └── ... ├── plugins/ │ ├── platforms/ │ │ └── libqxcb.so │ ├── imageformats/ │ └── ... ├── qt.conf └── AppRun # 如果加了-appimage参数才会生成注意qt.conf这个文件,它告诉Qt可执行文件去哪找插件和库。如果没有它,程序会去系统的绝对路径找Qt库,那就前功尽弃了。linuxdeployqt一般会自动生成,你也可以手动检查里面内容,通常类似:
[Paths] Prefix = . Plugins = plugins4.3 生成AppImage单文件(可选)
如果你想要一个双击就能运行的AppImage,需要再装一个辅助工具linuxdeploy。过程不复杂,本质是把刚才的release目录打包进AppImage的squashfs文件系统里。不过AppImage方式对系统缺少FUSE的机器兼容性要差一些,我一般更推荐直接用目录方式发布,拷给别人的时候打个tar.gz就行。
4.4 验证打包结果
打包完成后,务必在一台没有安装Qt的干净机器或容器里测试。最简单的验证方法:
export LD_LIBRARY_PATH=./lib ./MyApp如果程序能正常弹窗,说明打包成功。如果报错,接下来看第5节的排查思路。
5. 常见问题与排查技巧实录
5.1 "could not find or load the Qt platform plugin xcb"
这是出现频率最高的问题。原因通常有三种:一是plugins/platforms/libqxcb.so不存在或没有执行权限;二是libqxcb.so依赖的一些系统库缺失(比如libxcb-iccc4、libxcb-image0、libxcb-keysyms1等);三是xcb相关库版本不对。
排查时先检查libqxcb.so在不在,再用ldd看它有没有unresolved的依赖:
ldd ./plugins/platforms/libqxcb.so | grep "not found"如果有not found,去系统里找对应的so装到lib/目录下,或者直接把系统对应包安装上:
sudo apt install libxcb-xinerama0 libxcb-iccc4-dev libxcb-image0-dev libxcb-keysyms1-dev libxcb-randr0-dev说实话,这一步很多教程不会写,但是实际复制libqxcb.so之前,先确认这些xcb辅助库都在,能省掉后面一堆麻烦。
5.2 提示"error while loading shared libraries: libQt5Core.so.5: cannot open shared object file"
这个属于最典型的RPATH/库路径问题。linuxdeployqt在可执行文件里写入了相对路径或绝对路径去指向lib/目录,但如果它没写对,或者程序在运行时因为某些原因覆盖了搜索路径,就会找不到库。
检查可执行文件的RPATH:
readelf -d MyApp | grep -i rpath正常情况应该看到类似$ORIGIN/lib的条目。如果为空,可以用patchelf手动加:
patchelf --set-rpath '$ORIGIN/lib' MyApp另外检查一下系统中是否装了和你打包版本冲突的Qt库,特别是在用户主目录下的.qmlc缓存、.config配置里可能有旧的路径缓存。
5.3 "This application failed to start because it could not find or load the Qt platform plugin"
这个报错和第一种的区别在于,即便libqxcb.so存在,它仍然加载失败。通常是因为libqxcb.so依赖的libQt5XcbQpa.so.5缺失,或者系统的GL库版本太低。
我的经验是,如果你打包用的Qt版本太新(比如5.15.2以上),在Ubuntu 18.04这类老系统上跑,极容易遇到GL库版本对不上的问题,因为Qt的xcb插件会去加载libGL。处理办法是额外拷贝一份libGL和libEGL相关的库进lib/目录:
cp /usr/lib/x86_64-linux-gnu/libGL.so.1 ./lib/ cp /usr/lib/x86_64-linux-gnu/libEGL.so.1 ./lib/ cp /usr/lib/x86_64-linux-gnu/libglapi.so.0 ./lib/有些家伙直接把系统的libGL目录整个拷进去,虽然有点粗暴,但确实能解决掉不少显示相关的问题。
5.4 程序能启动但显示"QXcbConnection: Could not connect to display"
这种问题常见于在没有图形界面的环境(比如纯终端服务器)里测试,或者SSH远程转发X11时权限不对。属于运行环境问题而非打包问题。确认你在有真实桌面会话的机器上测试就行。
5.5 打包时出现"ERROR: The host system is too old for this version of Qt"
这个报错一般是因为linuxdeployqt检查了Qt版本和宿主系统glibc版本之间的关系,认为目标系统太老。解决方法是给linuxdeployqt传参跳过检查:-unsupported-allow-前缀的参数,比如-unsupported-allow-new-glibc在某些版本里叫这个,具体用--help看。
不过注意,这属于危险操作。如果目标机器glibc版本确实太低,跳过检查后程序依然可能跑不起来。从源头解决还是要选择与你目标机器glibc兼容的Qt版本。
5.6 打包后体积异常大,又不知道怎么精简
默认linuxdeployqt会把Qt库各种依赖都拷过来,几MB的程序打包成一百多MB很常见。精简的话,可以自己控制复制范围:不直接让linuxdeployqt全量复制,而是手动选择lib/和plugins/下的文件。
一个比较实用的精简策略是:保留core、gui、widgets、network这几个主库,删掉qml相关的大块头;插件只保留platforms和imageformats这两个目录,其他按需补充。实测一个串口工具,从160MB能精简到60MB左右,当然前提是程序没用到QML。
6. 文章最后的实操心得
这一路踩坑踩下来,我自己有个习惯固定下来了:每次打包前,先在打包目录里放一个run.sh脚本,内容大概是:
#!/bin/bash # 确保Qt库路径正确 SELF_DIR="$(cd "$(dirname "$0")" && pwd)" export LD_LIBRARY_PATH="$SELF_DIR/lib:$LD_LIBRARY_PATH" export QT_PLUGIN_PATH="$SELF_DIR/plugins" exec "$SELF_DIR/MyApp" "$@"这样即使linuxdeployqt生成的RPATH有点问题,靠着这个脚本也能兜底。很多Linux用户在遇到platform plugin报错时,都会习惯性地用export QT_DEBUG_PLUGINS=1去调试,日志会精确打出插件加载失败的原因,这个参数强烈推荐——它是我排查Qt打包问题时的第一把钥匙。
如果你照着上面的流程走,绝大多数问题都能解决。实在不行,还有个笨办法:直接把整个Qt安装目录里的lib和plugins拷过去,虽然大,但确实什么都不会缺。个人体会是,打包这件事别追求一步到位,先跑通,再优化体积和兼容性,这才是靠谱的节奏。
本文还有配套的精品资源,点击获取