
开头做国产化适配的兄弟们应该都有体会在银河麒麟V10上开发Qt应用不是最难的真正折磨人的是把程序交给现场工程师之后对方一句双击打不开就能让你瞬间破防。依赖库缺失、平台插件找不到、权限不对、甚至解压路径带空格都能给你整出幺蛾子。我前前后后在这条路上踩了将近一个月的坑终于把linuxdeployqt在银河麒麟V10上打包Qt5.14.2应用的完整流程趟平了。这篇文章不说废话直接把我验证过的打包流程、参数选择、以及各种报错的处理办法整理出来。不管你是刚接触国产系统适配的新手还是被Qt打包折磨过的老手这套保姆级流程应该都能帮你省下不少折腾的时间。需要说明的是以下方案基于我实际使用的银河麒麟V10SP1/SP2版本均可和Qt5.14.2环境不同小版本可能略有差异但核心思路是通用的。1. 打包前的准备理解linuxdeployqt的工作原理1.1 为什么要用linuxdeployqt而不是直接拷贝很多刚上手的人会问我把编译出来的可执行文件和相关so直接拷到目标机器上不就行了问题在于一个正常的Qt程序运行时依赖的库远比表面看到的要多。且不说Qt自身的libQt5Core.so、libQt5Widgets.so这些光是平台插件libqxcb.so、xcb相关的一系列系统库就够你手动拷贝到崩溃。用得最多的场景是开发机上编译好的程序要部署到另外一台没有安装Qt环境的机器上运行。此时程序需要找到所有依赖的动态库而linuxdeployqt的作用就是用ldd递归扫描可执行文件的依赖关系把需要的so文件收集到同一个目录下然后通过patchelf修改可执行文件里的RPATH让程序运行时优先从本地目录加载这些库。这个逻辑相当于把供应链全部收敛到一个文件夹里目标机器上有没有Qt环境都无所谓了。我第一次手动拷贝时光补齐依赖就折腾了两天。先是用ldd查看缺失库一个一个去系统目录找找到之后拷贝过来结果发现这个so又依赖另外几个so典型的查一个冒出来三个。用了linuxdeployqt之后这个递归扫描和收集的过程被自动化了效率完全不在一个量级。1.2 银河麒麟V10环境的特殊之处银河麒麟V10基于Debian系发展而来但跟Ubuntu、Debian的软件包管理还是有一些细节差异。首先它的glibc版本相对固定如果你是在较新的Ubuntu上编译的Qt程序拿到麒麟上大概率会因为GLIBC_XX.X.X找不到而直接崩掉。所以最稳妥的方式是在目标版本的银河麒麟系统上编译Qt程序或者至少保证编译环境与运行环境的大版本一致。其次银河麒麟对Qt库的安装位置可能与通用Linux发行版不同如果你的程序是通过系统包管理器装的Qt比如apt install qt5-default这种方式linuxdeployqt默认搜索的qmake路径可能找不到需要手动指定。另外要特别注意架构问题。银河麒麟V10既有x86_64版本也有基于飞腾、鲲鹏的ARM64版本还有基于龙芯的MIPS64版本。linuxdeployqt工具本身需要与目标架构匹配不能拿x86_64的工具去打ARM64的程序。这个不算坑只是容易忽略一旦用错打包出来的程序在目标机器上根本跑不起来。1.3 工具与依赖清单在正式开始之前先列一下需要准备的东西银河麒麟V10系统开发机和目标机尽量同版本Qt 5.14.2建议使用官方安装包安装到自定义目录比如/opt/Qt5.14.2linuxdeployqt工具建议下载源码自行编译或从官方release获取patchelf工具linuxdeployqt会用到部分版本需要自己装基本的构建工具链如果还要现场改代码重编gcc、g、make提示在银河麒麟上使用官方Qt安装包时可能需要手动给安装程序加执行权限并且安装过程中如果遇到缺少xcb库的提示这是正常的后续打包时linuxdeployqt会帮你把插件带上。2. 核心打包流程linuxdeployqt安装与基础配置2.1 获取linuxdeployqt的正确姿势linuxdeployqt官方GitHub的releases页面提供了预编译的二进制文件但我在实际使用中发现预编译版本在某些银河麒麟环境下存在兼容性问题主要是依赖的glibc版本过高。如果遇到这种情况建议直接从源码编译。源码编译本身不难依赖Qt开发环境和几个工具链组件。编译前先确认系统里有没有必要的工具没有就装上sudo apt update sudo apt install git g make patchelf然后克隆源码并编译git clone https://github.com/probonopd/linuxdeployqt.git cd linuxdeployqt qmake make -j$(nproc)编译完成后当前目录下会生成一个linuxdeployqt可执行文件。建议把它放到/usr/local/bin下方便全局调用sudo cp linuxdeployqt /usr/local/bin/ sudo chmod x /usr/local/bin/linuxdeployqt这里有个关键点编译linuxdeployqt所用的qmake版本最好与你要打包的Qt版本一致或接近否则在打包时可能出现版本判断上的问题。我当时用的就是系统里自带的Qt5.15版本编译的linuxdeployqt然后拿它去打包Qt5.14.2的程序没有出现版本冲突但保险起见还是建议直接用Qt5.14.2的qmake来编译。2.2 工程构建方式与编译参数确认在打包之前先确认你的Qt程序编译Release版本并且确保编译过程中没有使用到静态链接的Qt库除非你有意为之。我一般习惯在Qt Creator里选择Release模式构建或者直接命令行mkdir build cd build /opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake ../你的工程.pro -spec linux-g CONFIGrelease make -j$(nproc)编译完成后不急于打包先在开发机上直接运行一下生成的二进制确认程序本身没有问题。如果开发机上运行就崩溃那大概率不是打包能解决的先排查代码或者编译参数。另外建议检查一下可执行文件链接的Qt库路径。用ldd命令看一下ldd ./你的可执行程序 | grep Qt正常情况下会显示指向/opt/Qt5.14.2这样的绝对路径这说明当前程序依赖的是你手动安装的Qt库。如果显示系统自带的Qt路径检查一下LD_LIBRARY_PATH环境变量或者qmake路径选择是否正确。2.3 三条关键打包命令打包的核心命令并不复杂但参数选择上有讲究。我的标准操作流程是三条命令第一条把可执行文件和基础依赖库全部收集起来linuxdeployqt ./你的可执行程序 -qmake/opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake这条命令会自动扫描依赖、拷贝库文件、设置RPATH。执行完以后当前目录下会多出lib文件夹存放收集到的动态库可执行文件的RPATH会被修改为相对路径。第二条补充Qt插件和翻译文件linuxdeployqt ./你的可执行程序 -qmake/opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake -qtdir/opt/Qt5.14.2/5.14.2/gcc_64-qtdir参数用于指定Qt的安装目录这样工具会把platforms、imageformats、iconengines等插件目录一并复制到打包目录下。我第一次没加这个参数打包出来的程序在目标机器上提示could not find the Qt platform plugin xcb就是因为缺插件目录。第三条如果需要生成桌面启动器文件desktop文件和图标linuxdeployqt ./你的可执行程序 -qmake/opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake -verbose2-verbose2可以输出详细的日志方便排查问题。桌面文件的创建不是必须的但这在交付给客户时体验会好很多双击桌面图标就能启动而不是跑到命令行敲路径。2.4 打包目录结构与交付前的检查项正常执行完上面的步骤后你的目录里应该包含以下内容可执行文件本体lib目录几十上百个so文件取决于程序复杂度platforms目录下面有libqxcb.so这个最重要imageformats目录jpg、png等格式支持插件translations目录Qt自带的翻译文件如果你指定了-desktopfile参数还会有对应的.desktop文件和图标交付之前我习惯做一次清理和自查find . -name *.so* -exec ls -l {} \;看看有没有异常大的文件超过100MB的debug版本库或者重复的库。另外检查一下可执行程序的RPATH设置是否正确patchelf --print-rpath ./你的可执行程序正常情况下会输出$ORIGIN或者$ORIGIN/lib这样的内容。如果这里显示的是绝对路径说明RPATH设置失败运行时会去查找系统目录而不是本地目录一旦目标机器上没装Qt就直接报错。3. 实操过程从编译到交付的完整记录3.1 一个真实案例五步完成打包我用一个实际项目来走一遍完整流程。假设程序叫MyApp编译后的二进制在/home/user/build/MyApp我计划打包到/home/user/deploy目录。第一步创建干净的部署目录复制可执行文件进去mkdir -p /home/user/deploy cp /home/user/build/MyApp /home/user/deploy/ cd /home/user/deploy第二步跑基础打包命令把依赖库牵扯进来linuxdeployqt MyApp -qmake/opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake执行完以后查看一下lib目录里有没有关键的库ls lib/ | grep -E libQt5|libicudata|libxcb正常情况下libQt5Core、libQt5Gui、libQt5Widgets这些核心库都已经在里面了icu相关的国际化库也会被收集进来。第三步补插件linuxdeployqt MyApp -qmake/opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake -qtdir/opt/Qt5.14.2/5.14.2/gcc_64这一步执行后目录下会多出platforms等插件目录。重点看下platforms/libqxcb.so是否存在这个文件缺失的话程序在目标机器上100%起不来。第四步验证打包目录是否完整自洽。把/home/user/deploy打包压缩拷贝到一个未安装Qt的干净系统上我一般用虚拟机模拟然后运行cd /home/user/deploy ./MyApp如果程序能正常弹出界面说明打包目录自洽。这一步千万别省我见过太多人打包出来在自己机器上能跑拷到客户那边就崩就是因为没有在干净环境里验证过。第五步如果验证通过再把desktop文件配置上整个交付物就完整了linuxdeployqt MyApp -desktopfileMyApp.desktop -qmake/opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake注意desktop文件里的Exec字段最好用绝对路径或者相对路径用$ORIGIN这类变量在有些桌面环境下不生效。填写可执行文件名的相对路径是最稳妥的。3.2 qmake路径选择的经验与误区上面反复提到-qmake参数这里展开讲一下为什么它这么重要。linuxdeployqt运行时需要判断当前程序的Qt版本、模块信息、插件目录位置这些信息统统来自qmake。如果系统里有多个Qt版本而不指定-qmake工具就会取PATH环境变量里的第一个qmake很可能不是你编译程序用的那个版本。一旦版本判断错误可能出现收集的库版本不匹配、插件路径错误等奇奇怪怪的问题。我之前踩过一个坑安装Qt时用sudo权限执行结果/opt/Qt5.14.2目录的属主变成了root普通用户运行时某些库文件无法读取程序启动失败。排查了半天最后用chmod -R arX /opt/Qt5.14.2把权限放开了才解决。所以安装Qt时建议用普通用户安装到用户目录或者装完后把权限调整好。另外如果目标机器上没有安装Qt开发环境打包时只需要把依赖库带上即可。但有一种情况需要注意你的程序如果用到了Qt的某些非核心模块比如SerialPort、Network这些模块的库通常也会被自动收集但插件目录里未必有对应的支持文件。以SerialPort为例它不依赖单独的平台插件但如果你用了WebEngine这种重型模块那打包目录会巨大多而且依赖的系统库也特别多这种情况建议改换思路考虑用系统包管理器安装Qt运行库而不是硬打包。3.3 权限、链接与lib目录的微调打包完成后还有一个常见的隐形杀手动态库的链接路径问题。linuxdeployqt默认使用$ORIGIN作为RPATH的基础但库和库之间也存在相互依赖关系。比如libQt5Gui.so本身依赖libQt5Core.so如果这两者的相对路径关系被破坏程序启动时照样找不到库。正常情况下linuxdeployqt已经把库和库之间的RPATH也修好了但我遇到过个别第三方库没被修的情况表现为程序启动时报错cannot open shared object file: No such file or directory而库文件明明就在lib目录里。这种情况下可以用patchelf手动修复patchelf --set-rpath $ORIGIN lib/某个第三方库.so这样该库在依赖其他库时会优先从当前目录查找。还有一种情况是某些库的链接头需要指向特定文件名比如libssl.so.1.1和libssl.so这种带版本号和不带版本号的差异。遇到这种直接创建软链接ln -sf libssl.so.1.1 lib/ssl/libssl.so把这些细节处理好打包目录才算真正稳定。4. 常见问题与排查技巧实录4.1 问题速查表遇到报错先对照这个我在多个版本的银河麒麟系统上反复测试过整理了一份高频问题对照表基本覆盖了90%以上的打包失败场景报错信息根本原因解决方案could not find the Qt platform plugin xcb缺少platforms/libqxcb.so执行打包时加上-qtdir参数或者手动拷入platforms目录error while loading shared libraries: libQt5Core.so.5RPATH未设置或库目录缺失确认lib目录存在且与可执行文件同级用patchelf手工设置RPATH为$ORIGIN/libGLIBC_2.29 not found编译环境glibc版本高于运行环境在目标版本的银河麒麟上重新编译或者换用更老的编译器Cannot mix incompatible Qt library程序链接了不同版本的Qt库检查LD_LIBRARY_PATH确保没有其他版本Qt混入重新用正确qmake编译qt.qpa.plugin: Could not find the Qt platform plugin linuxfb平台插件选错或缺失确认编译时使用的是xcb而不是linuxfb检查plugins目录完整性双击图标无反应命令行直接运行有输出desktop文件Exec路径配置错误修改.desktop文件的Exec字段为绝对路径或正确的相对路径4.2 最折磨人的缺so问题终极解决思路缺so文件这个问题看起来简单实际排查起来能让人崩溃。因为一个so缺失背后往往跟着一串连锁反应。比如程序启动报错少了一个libxxx.so你拷过来后发现这个so又依赖另外一个libyyy.so再拷又发现libyyy.so还依赖libzzz.so。周而复始心态直接就崩了。后来我总结出一个高效率的排查方法直接用ldd配合脚本递归扫描。一条命令就能看到全貌ldd ./MyApp | grep not found把所有not found的库名记下来然后去开发机系统目录里找对应的so文件拷贝到lib目录。不要一个一个试先一次性收集完再测试。对于某些特殊的库比如私有协议的加密库、特定的硬件驱动库linuxdeployqt可能无法自动识别这类非标依赖我的建议是手动拷贝并在文档里明确记录因为一旦遗漏目标机器上极难排查。还有一个容易忽略的地方如果程序内部用插件机制原生加载了某些.so不是链接而是运行时dlopen这些库不会出现在ldd的输出里linuxdeployqt也扫不到。这种库必须手动打进部署目录并且在代码里用相对路径或者可配置路径加载。我在交付一个带第三方算法模块的项目时就吃过这个亏程序跑起来界面上提示插件加载失败排查了整整半天才想到是模块路径问题。4.3 ARM64平台的额外注意事项如果你在飞腾或者鲲鹏这类ARM64平台的银河麒麟上打包Qt程序有几点和x86_64平台完全不同需要特别注意。第一是linuxdeployqt工具本身必须是ARM64版本不能拿x86版本凑合。第二是qmake的路径需要对应gcc_arm_64目录或者类似命名的路径不是x86_64的gcc_64目录。第三是有些x86上常见的系统库在ARM平台上版本号或者名称存在差异比如某些libX开头的库在ARM版银河麒麟上可能不叫这个名字。遇到这种情况不能照搬经验建议在目标平台上重新执行ldd排查一遍。还有一点很实际ARM平台的虚拟机效率普遍偏低如果条件允许尽量在实体机上编译和打包不然光是编译Qt程序就要比x86平台慢好几倍排查问题也会因为系统响应慢而格外煎熬。4.4 打包后的性能与体积优化建议很多人在打包完成后就着急交付其实还有两个可以优化的点。第一个是体积。如果打包目录里有大量的.so文件可以先看看有没有不必要的库混进来。比如程序只用了Qt Widgets模块但lib目录里却有libQt5WebEngineCore.so这种几百MB的重型库明显是打包工具过度收集了。我遇到过linuxdeployqt把一些通过dlopen加载的库也收进来而这些库实际上根本没有被用到的情况。手动删除不需要的库可以显著缩小交付包体积。第二个是启动速度。打包目录中的库文件如果没有经过strip处理体积偏大加载速度也会受影响。可以使用strip命令给库文件瘦身find lib/ -name *.so* -exec strip --strip-unneeded {} \;这一步能把库体积平均缩小30%到50%特别是WebEngine这类巨型库strip效果非常明显。我自己的项目经过strip之后整个部署包从450MB降到约280MB启动时间也快了不少。当然strip前记得备份原始库文件因为有些带调试信息的库被strip之后后续如果还想用gdb调试就没办法了。如果你需要现场调试排障建议保留一个未strip的调试版本。4.5 现场交付的兜底方案写一个启动脚本即使打包步骤全部正确客户机器的环境差异还是可能造成各种意外。比如客户系统缺少某个系统级的运行库或者库版本不匹配。针对这种情况我习惯在打包目录里放一个启动脚本用脚本来动态修正一些环境变量给程序留出最后一道保险。一个简单实用的启动脚本如下#!/bin/bash BASE_DIR$(cd $(dirname $0) pwd) export LD_LIBRARY_PATH$BASE_DIR/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM_PLUGIN_PATH$BASE_DIR/platforms exec $BASE_DIR/MyApp $脚本的核心逻辑是根据脚本自身所在路径动态设置LD_LIBRARY_PATH和插件路径这样即使desktop文件里的工作目录不对程序也能找到该找的东西。这个脚本在当前目录执行和从其他目录执行都能正常工作。把脚本加到desktop文件里Exec字段写脚本的路径而不是可执行文件的路径效果会稳定很多。注意启动脚本不要写死绝对路径要用脚本自身位置来推导。因为客户把目录放在哪个位置完全不可控绝对路径的脚本挪个地方就废了。5. 从开发到交付的全面检查清单5.1 编译阶段的检查这一节把整个流程的检查事项汇总一下方便读者复制过去当清单用。不要等到打包完了再回头排查每个阶段都检查到位能省一大半调试时间。编译阶段确认使用Release模式编译不要带-g调试符号除非你有特别需求确认使用的Qt版本是5.14.2可以通过qmake --version查看确认没有引用到系统自带的其它版本Qt库用ldd检查确认程序在开发机上可以正常运行再进入打包流程打包阶段使用与编译一致的qmake路径执行linuxdeployqt指定正确的-qtdir参数确保平台插件被收集用-verbose2参数查看完整日志留意warning提示确认lib目录不为空且包含libQt5Core等核心库确认platforms/libqxcb.so存在确认可执行文件的RPATH为相对路径交付前验证在未安装Qt的干净机器上运行测试从头测试一遍程序所有核心功能检查桌面启动器是否能正常拉起程序工作目录是否正常检查程序的数据文件、配置文件路径是否依赖绝对路径如果是切换到相对路径方式将整个部署目录压缩后在目标机器上解压测试建议用tar.gz格式而不是zipzip容易丢失文件权限位5.2 交付物的目录结构参考为了让你有个直观的参照我贴一个标准交付目录的结构MyApp_deploy/ ├── MyApp # 可执行文件 ├── start.sh # 启动脚本 ├── MyApp.desktop # 桌面配置文件 ├── icon.png # 应用图标 ├── lib/ # 动态库目录 │ ├── libQt5Core.so.5 │ ├── libQt5Gui.so.5 │ ├── libQt5Widgets.so.5 │ ├── libicudata.so.56 │ ├── ... ├── platforms/ │ └── libqxcb.so ├── imageformats/ │ ├── libqjpeg.so │ └── libqpng.so ├── translations/ │ ├── qt_zh_CN.qm │ └── qt_base_zh_CN.qm └── 你的数据目录/ # 如果有外部资源文件的话这个结构不是固定的但一定要保证可执行文件 lib platforms这三件套的完整。另外注意translations目录里的qm文件如果你的程序做了多语言界面务必确认对应的qm文件已包含进来否则客户那边英文显示不全、中文显示乱码都是可能的。5.3 长期维护视角的补充建议打包交付其实是一个持续的过程不是一次性工作。如果你的程序后续要更新版本建议把打包流程脚本化。我的做法是写一个package.sh脚本放在工程根目录每次发布新版本时一键执行确保打包参数的一致性避免手动操作带来的遗漏。脚本里我会把关键路径抽成变量#!/bin/bash QT_PATH/opt/Qt5.14.2/5.14.2/gcc_64/bin/qmake APP_NAMEMyApp BUILD_DIRbuild DEPLOY_DIRdeploy # 编译 cd $BUILD_DIR $QT_PATH ../MyApp.pro -spec linux-g CONFIGrelease make -j$(nproc) # 准备部署目录 rm -rf $DEPLOY_DIR mkdir -p $DEPLOY_DIR cp $APP_NAME $DEPLOY_DIR/ # 打包 cd $DEPLOY_DIR linuxdeployqt $APP_NAME -qmake$QT_PATH -qtdir$(dirname $(dirname $QT_PATH))脚本化的好处除了省时间还能保证每次交付的包质量一致不会因为某次忘了加参数导致交付包残缺。我把这个脚本放在工程里每次有同事接手项目直接跑一遍脚本就能完成打包学习成本也低。写在最后打包Qt应用这件事说难也难说不难也不难。难的是整个链路里有太多细节任何一个环节断了链子都会体现在结果上不难的是只要你把流程标准化、把常见坑的解决方案沉淀下来就不会反复在同一个地方跌倒。用linuxdeployqt在银河麒麟V10上打包Qt5.14.2应用最核心的点其实就三个保证打包工具和qmake路径正确、保证插件目录完整、保证目标环境的clean test通过。把这三件事做扎实你的交付过程基本就稳了。最后再分享一个小技巧每次打包完把linuxdeployqt的完整输出日志保存一份标注好日期和Qt版本。一旦后续客户那边出了奇怪的问题翻出当时的日志对照排查往往能很快定位问题。毕竟打包工具跑起来的时候能暴露的问题在日志里基本都有线索就怕你没有留下记录。希望这篇保姆级的流程能帮你少走一些弯路把更多精力放在应用功能本身。