资讯动态

银河麒麟系统下Qt程序手动打包全流程详解

发布时间:2026/10/3 8:01:20 来源:尧图企业网站定制
在Linux上跑Qt程序最尴尬的时刻不是程序崩溃而是你兴冲冲地把可执行文件压缩包发给同事对方解压后双击图标屏幕上弹出一句error while loading shared libraries: libQt5Core.so.5: cannot open shared object file: No such file or directory然后整个办公室都安静了。我在麒麟系统上第一次干这事的时候就栽过这个跟头后来才发现Linux下的Qt程序根本不是一个可执行文件拷贝过去就能跑的你交付的应该是一整套“Qt运行环境的最小子集”。这篇文章我就完整记录一遍在银河麒麟系统上手动打包Qt程序的过程从依赖分析、目录组织、RPATH修改到平台插件处理、桌面入口文件编写再到各种我实际交付时踩过的坑一次性讲透。看完之后你至少能打出一个在相同发行版上解压即跑的tar包想做得再正式一点也能自己扩展成deb安装包。内容偏实操但我会把每一步背后的原理也讲清楚方便你举一反三。1. 打包前必须盘清楚的四件事1.1 麒麟系统的“出身”决定后面怎么干活很多人一听“麒麟系统”以为只有一个版本实际上麒麟这个家族的情况比想象中复杂。就我接触过的银河麒麟V10来说桌面版大多走的是Ubuntu血统底层包管理是apt而部分服务器版则带CentOS血统用的是yum或dnf。这个差异直接影响你后面敲命令的方式也影响目标机器上预装了什么基础库。接手一个打包任务第一件事不是写脚本而是先看环境。在开发机上跑两条命令cat /etc/os-release uname -a输出里能看到系统是基于哪个发行版做的内核版本是多少架构是x86_64还是aarch64。别凭感觉猜也别只看桌面右上角那个Logo麒麟的不同子版本可以长得一模一样但底座差异会让你在依赖上栽跟头。这个检查的意义不只是确认包管理器更重要的是判断目标用户那边的运行环境大概率是个什么状态。通常我们会要求目标机器的系统版本和开发机保持一致或相近如果差出好几个大版本后面提到的GLIBC问题会让你束手无策提前锁定版本范围能省掉大量返工。1.2 Qt是怎么装进系统里的决定依赖来源Qt在Linux上的安装方式太杂了这直接影响打包时依赖库从哪里找。最常见的三种用官方安装器装的比如qt-opensource-linux-x64-5.15.2.run默认装到/opt/Qt/5.15.2/gcc_64这种目录下Qt自身的所有库都在这个目录的lib子目录里。用系统包管理器装的比如sudo apt install qtbase5-dev库会散落在/usr/lib/x86_64-linux-gnu/将来目标机器能不能匹配上就不好说了。自己源码编译的库可能在某一个自定义目录里这种情况依赖路径最不可控。我个人的建议是正式交付的Qt程序尽量使用官方Qt工具链编译并采用release构建。打包时从安装目录里把对应库收集出来这样你能明确知道自己交付的是哪个Qt小版本也好控制体积。用系统自带的Qt版本最大的问题是版本不可预期目标机器上如果装了一个更老的Qt你一启动就崩连排查方向都难找。1.3 位数和编译器的“血型”要对齐程序打包前用file命令看一眼可执行文件的格式这一步简单但能避免后面很多玄学file myapp输出里会看到ELF 64-bit LSB executable, x86-64之类的信息。只要开发机和目标机都是64位问题不大。怕的是有些项目组为了兼容老旧硬件用了32位编译环境那就得把所有32位依赖库也一起打包复杂度直接翻倍。另外看下qmake版本确认编译用的Qt版本qmake -vQt版本号很关键因为4.x和5.x的程序打包方式截然不同5.x内部不同小版本之间库文件名也可能有细微差异。把这些信息记下来写进项目的发布说明里别嫌麻烦这对后面排查问题很有用。1.4 工具准备ldd、patchelf、file一个都不能少手动打包的核心工具其实是这三个ldd查依赖、patchelf改动态库路径、file看文件格式。其中ldd和file一般是系统自带的patchelf需要手动装sudo apt install patchelf在我测试过的环境下patchelf这工具很小但对Linux来说它几乎是打包必备。它的作用是修改ELF文件里的解释器和依赖库搜索路径相当于Windows下改exe的清单文件。没有它你就只能靠LD_LIBRARY_PATH环境变量硬凑问题多得很。另外强烈建议准备一台干净的虚拟机装好和客户一致的系统版本专门用来做验证。我第一次打包后在开发机上跑得飞起发过去就废就是因为开发机上早已装了一堆Qt开发库掩盖了依赖缺失的问题。有一个干净的验证环境才能测出真正的交付状态。2. 把依赖关系理清楚打包才不会抓瞎2.1 动态链接库是怎么被找到的Linux上的可执行文件默认是不在自己所在目录里找.so文件的这跟Windows习惯完全不一样。动态链接器ld.so有一套固定的搜索顺序大致是ELF文件里记录的RPATH/RUNPATH然后是环境变量LD_LIBRARY_PATH再然后是/etc/ld.so.cache里的缓存列表最后才是/lib和/usr/lib这类系统目录。如果这几条路都找不到库程序就报cannot open shared object file。很多时候你在终端里运行正常是因为你之前 export 过LD_LIBRARY_PATH或者你长期开着某个Qt开发环境终端启动时自动把库目录加了进去。可用户双击桌面图标的那一刻完全没有这些环境变量程序自然找不到库。理解这个机制你就明白打包的核心任务是什么了不是“把程序文件拷过去”而是“确保程序在没有任何额外环境变量的前提下也能在自己周边的目录里找到所有依赖库”。2.2 用ldd看清程序的“关系网”ldd是打包过程中用得最多的命令。对一个编译好的Qt程序执行ldd myapp输出会列出一堆.so文件及其绝对路径。你要学会从这一堆东西里分门别类linux-vdso.so.1、ld-linux-x86-64.so.2、libc.so.6、libm.so.6这类属于系统基础库目标Linux系统一定会有通常不需要打包。libstdc.so.6、libgcc_s.so.1属于编译器运行时库目标机器一般也有但版本可能比你编译机老这个后面单独说。libQt5Core.so.5、libQt5Widgets.so.5、libQt5Gui.so.5这类就是真正的Qt运行库必须打包。一个关键动作是Qt库自己本身还有依赖。比如libQt5Gui.so.5会去依赖libpng、libz、各种xcb相关库。所以仅对可执行文件执行一次ldd不够常见做法是“递归依赖收集”也就是把可执行文件依赖的Qt库全部列出再对每一个Qt库执行一次ldd把它们的依赖也一并收集。这个过程可以脚本化后面我会给一个可直接用的脚本。2.3 为什么linuxdeployqt不是万能的可能你会问别人不都是用linuxdeployqt一键打包吗这工具确实存在思路也模仿了Windows上的windeployqt但它已经很久没有维护了而且对Qt高版本的支持时好时坏。在麒麟系统上我试过用它打包经常在DBus、OpenSSL插件、xcb插件这些环节报错处理起来反而比手动更费劲。手动打包看起来麻烦但它的好处是每一步都能审计你知道自己拷了哪些库、改了什么路径、跳过了什么出问题能精准定位。对一个要交付给外部客户的产品来说这种可控性远比“一键完成”重要。而且手动流程一旦跑通后续每次发布最多十分钟的事。3. 手动打包完整实操从裸编译产物到可发布压缩包3.1 第一步搭建AppDir目录骨架打包的第一步不是拷库而是先规划目录结构。我习惯用AppDir这种风格组织原因很简单这套结构改造成deb、AppImage都很好扩展而且层次清楚。AppDir/ ├── myapp.desktop └── usr/ ├── bin/ │ └── myapp ├── lib/ │ └── libQt5Core.so.5 等依赖库 ├── plugins/ │ └── platforms/ │ └── libqxcb.so └── share/ └── icons/ └── hicolor/ └── 256x256/ └── apps/ └── myapp.png先把目录建出来mkdir -p AppDir/usr/bin mkdir -p AppDir/usr/lib mkdir -p AppDir/usr/plugins/platforms mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps为什么要把可执行文件放在usr/bin、库放在usr/lib因为这种布局下从可执行文件所在目录的视角看相对路径非常规整../lib就是库目录。后面设置RPATH时会直接用$ORIGIN/../lib非常干净。3.2 第二步用ldd生成依赖库清单并批量拷贝现在假定你的可执行文件叫myapp已经编译好并且是release版本。先看它依赖了哪些Qt库ldd myapp | grep Qt假设你用的Qt装在/opt/Qt/5.15.2/gcc_64那么Qt相关库的路径都会带这个前缀。手动拷贝时最保守但可靠的做法是把所有以该前缀路径出现的.so文件复制到AppDir/usr/lib/使用cp -L参数把符号链接指向的真实文件拷贝出来避免交付的包里出现一堆断链的软链接。cp -L /opt/Qt/5.15.2/gcc_64/lib/libQt5Core.so.5* AppDir/usr/lib/ cp -L /opt/Qt/5.15.2/gcc_64/lib/libQt5Gui.so.5* AppDir/usr/lib/ cp -L /opt/Qt/5.15.2/gcc_64/lib/libQt5Widgets.so.5* AppDir/usr/lib/这里有一个原则要强调程序依赖的Qt库必须打包系统基础库不建议打包。原因很简单目标机器上libc、libstdc这些库几乎一定存在你把自己的版本打过去反而可能因为版本过低或过高覆盖别人的行为容易出稀奇古怪的冲突。光拷这几个Qt库还不够因为Qt库自身还依赖其他库。比如libQt5Gui.so.5依赖libpng、libz、libxcb等这部分依赖往往不在Qt安装目录下。你需要对拷过来的每个Qt库再执行一次ldd把非系统路径下的依赖也拷过来。这一步是手动打包最枯燥但最关键的环节建议直接写成脚本循环处理。3.3 第三步拷贝Qt平台插件如果程序用到了QApplication那几乎所有界面程序都离不开一个关键插件platforms/libqxcb.so。缺少这个文件时程序启动会报类似could not find or load the Qt platform plugin xcb的错误。在Qt安装目录下找到这个插件cp -L /opt/Qt/5.15.2/gcc_64/plugins/platforms/libqxcb.so AppDir/usr/plugins/platforms/同时也要检查这个插件自身的依赖ldd AppDir/usr/plugins/platforms/libqxcb.so你会看到一堆libxcb-*相关的库这些是Qt界面能否正确显示的关键。如果目标机器上已经自带这些库那就不用拷如果不确定稳妥做法是把开发机上/usr/lib/x86_64-linux-gnu/下的相关libxcb-*.so.*也拷贝到AppDir/usr/lib/里并用RPATH指过去。这一步有个度的问题拷多了体积膨胀拷少了目标机器缺库我建议以在干净虚拟机上实际运行为准多试几轮就找到最优解了。另外如果你的程序用了数据库驱动Qt SQL、网络TLS、图片格式插件等对应的plugins/sqldrivers、plugins/imageformats、plugins/tls目录也要按需拷贝规则和platforms一样。Qt模块用得越多需要处理的插件越复杂尤其是Qt WebEngine这类大块头不只是插件还有一堆资源目录那种情况更建议考虑集成方案而不是纯手动打包。3.4 第四步用patchelf修正RPATH路径这一步是整个手动打包的核心。可执行文件和库都拷到目录里了但如果你不去修改ELF文件里的RPATH运行时还是不知道去../lib里找库。连接器默认不会因为你把库放在它旁边就自动识别这和Windows差异很大。对可执行文件执行patchelf --set-rpath $ORIGIN/../lib AppDir/usr/bin/myapp注意$ORIGIN一定要用单引号包住或者写成\$ORIGIN。我见过不少人在这一步翻车用双引号导致shell把$ORIGIN当成变量展开成空字符串结果RPATH被设置成了/lib之类的错误路径程序反而连系统的库都找不到了。$ORIGIN的含义是“可执行文件自身所在的目录”所以$ORIGIN/../lib对AppDir/usr/bin/myapp来说解析结果正好是AppDir/usr/lib这个写法在可执行文件被移动到任何位置的情况下都能正确解析具有很强的可移植性。完了之后还要对libqxcb.so执行一次patchelf --set-rpath $ORIGIN/../../lib AppDir/usr/plugins/platforms/libqxcb.so因为插件文件在AppDir/usr/plugins/platforms/下它的$ORIGIN指向的是platforms目录往上退两级才是AppDir/usr/lib。修改完后要验证别直接就跑readelf -d AppDir/usr/bin/myapp | grep -i path输出里应该能看到RPATH或RUNPATH并且路径是$ORIGIN/../lib。如果程序还依赖了你自己编译的其他.so这些自定义库如果也放在了AppDir/usr/lib/就不需要额外处理如果它们各自有自己的子目录要分别给它们设置对应的RPATH这个规则是一致的。3.5 第五步用qt.conf和启动脚本双保险RPATH解决的是程序启动时加载.so的问题但Qt插件搜索路径和RPATH是两回事。Qt在运行时查找插件的顺序主要依据编译时的QLibraryInfo::PluginsPath而这个路径在交叉拷贝到别的机器后往往是失效的。要让它找到位于AppDir/usr/plugins下的插件最直接的办法是在可执行文件旁边放一个qt.conf[Paths] Prefix../ Pluginsplugins Librarieslib放在AppDir/usr/bin/qt.confQt启动时读到这个文件就会以可执行文件所在目录为基准拼接出插件目录AppDir/usr/plugins从而正确加载libqxcb.so。这个文件看似简单但少写一行就可能导致程序因为找不到插件而直接崩溃而且崩溃信息还不直观。再配合一个start.sh启动脚本作为备选方案#!/bin/bash DIR$(cd $(dirname $0) pwd) export LD_LIBRARY_PATH$DIR/../lib:$LD_LIBRARY_PATH export QT_PLUGIN_PATH$DIR/../plugins $DIR/myapp $给start.sh加执行权限后用户在终端跑这个脚本也能启动。但说实话我最终交付更依赖RPATH加qt.conf的方案因为用户体验最好用户直接双击桌面图标就能跑不用开终端也就少了一层“看不懂报错就来找你”的概率。3.6 第六步编写desktop文件并打包压缩为了让程序能出现在系统应用菜单和桌面上需要写一个.desktop文件。内容大致如下[Desktop Entry] TypeApplication NameMyApp Name[zh_CN]我的应用 CommentMy Qt Application Exec/opt/myapp/usr/bin/myapp Icon/opt/myapp/usr/share/icons/hicolor/256x256/apps/myapp.png Terminalfalse CategoriesDevelopment; StartupNotifytrue注意这个文件里的Exec和Icon必须写成绝对路径因为桌面环境启动程序时不会做相对路径解析写相对路径大概率直接没反应。我给客户的交付约定是统一解压到/opt/myapp所以desktop文件里也写死这个路径。把desktop文件放到AppDir/myapp.desktop图标放到对应目录给文件加执行权限chmod x AppDir/usr/bin/myapp chmod x AppDir/myapp.desktop最后打包成压缩包推荐在AppDir上一级目录执行tar -czvf myapp-v1.0.0.tar.gz AppDir这样用户拿到的是myapp-v1.0.0.tar.gz解压后就是一个结构完整的AppDir目录复制到/opt/myapp就能用。3.7 附加思路如果要做成deb安装包如果你的交付对象习惯安装软件包或者程序需要接入系统应用商店那deb是更正式的选择。deb的本质是约定好目录结构加上一段控制信息myapp-deb/ ├── DEBIAN/ │ └── control └── opt/ └── myapp/ └── (从AppDir里拷贝过来的usr等目录)control文件最少需要写清包名、版本、依赖和描述Package: myapp Version: 1.0.0 Architecture: amd64 Maintainer: Your Name youexample.com Depends: libc6 ( 2.17), libstdc6 ( 5) Description: My Qt Application for Kylin OS然后用dpkg-deb -b myapp-deb myapp.deb就能生成deb包。不过给内部用户交付时tar.gz已经足够deb主要用于上架应用商店或需要自动处理依赖的场景也可以作为后续优化方向来做。4. 麒麟系统下避坑记录与常见问题排查4.1 常见问题速查表我把在麒麟系统上实际遇到的高频问题整理成了一张表每一条背后都对应一次真实的排查经历。现象根本原因解决办法启动报error while loading shared libraries: libQt5Core.so.5RPATH未设置或库没拷进包用patchelf设置$ORIGIN/../lib确认库在usr/lib下启动报could not find or load the Qt platform plugin xcbplatforms/libqxcb.so缺失或qt.conf没写拷贝xcb插件并在可执行文件旁放好qt.conf启动后界面一闪就退出终端报各种xcb符号错误libQt5Gui.so.5或libqxcb.so缺少底层xcb相关依赖对libqxcb.so执行ldd补齐libxcb-*以及libxkbcommon等库程序能启动但所有中文字体变成方块目标系统缺少中文字体打包时附上字体文件或在程序里用QFontDatabase::addApplicationFont加载字体在开发机一切正常在客户机器上报GLIBC版本错误编译机glibc版本高于目标机在低版本环境中重新编译或用与目标系统版本一致的构建机出包双击desktop图标没反应终端运行正常desktop文件里Exec路径错误或环境变量缺失检查desktop文件的Exec是否写绝对路径先手工执行该路径验证4.2 终端能跑、双击打不开的真相这个问题几乎每个做Linux桌面应用的人都遇到过。终端里能跑是因为你的shell环境里可能有各种历史遗留的LD_LIBRARY_PATH、PATH或者你当前目录就在某个Qt库目录附近。而桌面环境和shell是隔离的环境变量完全不继承。排查这类问题时最有效的办法是在desktop文件的Exec行后面临时加上QT_DEBUG_PLUGINS1也就是写成Execenv QT_DEBUG_PLUGINS1 /opt/myapp/usr/bin/myapp这样双击桌面图标后程序会把自己加载每个插件的过程全部打印出来通常直接就能看到是哪个插件路径找错了。修完之后记得把这段调试环境变量去掉。另外还有一种看似低级但真实高频的坑usr/bin/myapp忘了加执行权限。文件在开发机上是可执行的但拷贝到压缩包再解压后权限可能变了。交付前养成习惯逐个检查myapp、.desktop、start.sh这三个文件的权限。4.3 GLIBC版本不一致打包阶段最无力的一关如果你的程序在客户机器上报出类似GLIBC_2.29 not found这样的错那很遗憾这不是通过打包技巧能绕过的。glibc是Linux最底层的基础库几乎所有的程序都依赖它而它有一个铁律高版本glibc编译的程序不能在低版本glibc的系统上运行。我在开发机上用的是Ubuntu 20.04底座而客户那边的麒麟V10早期版本底座是Ubuntu 16.04两者glibc版本差了好几档编译好的Qt程序发过去就是报GLIBC错误。后来我专门在虚拟机里装了一台与客户系统版本一致的构建机所有交付版本都在上面编译这个问题才彻底解决。所以如果你是做商业交付提前确认客户系统的底座版本准备好对应的构建环境远比在打包环节跟这个错误死磕明智。这属于“打包前就要规划”的事情等报错再补已经晚了。4.4 一堆低级错误踩中一个就白干半天除了上述技术难点还有一些低级的坑让人哭笑不得我列出来提醒一下。拷贝库时用cp而不是cp -L。Qt安装目录下的.so文件大量是符号链接直接cp会把链接原样拷过去链接目标又不在包里用户一运行就是“找不到符号链接目标”。对release版的库执行strip来减小体积这个可以做但不要对debug版库做也不要在没测试的情况下批量strip所有库。strip过头导致程序启动后缺符号崩溃的例子我看过不止一次。建议发布流程里留一个“strip后进行完整功能回归”的检查点。程序用到了自定义的配置文件、字体、图标资源但打包时只拷了可执行文件和.so没有把资源目录一起放进去。这会导致程序启动正常但功能跑着跑着开始缺资源。建议构建脚本里显式声明资源目录的拷贝规则不要靠记忆。4.5 一个可直接抄的依赖收集脚本最后分享一个我在依赖收集阶段常用的脚本。它做三件事把可执行文件依赖的非系统库拷贝到AppDir/usr/lib对已拷贝的库递归补依赖最后统一执行一次RPATH修正。#!/bin/bash set -e APPmyapp QTDIR/opt/Qt/5.15.2/gcc_64 APPDIR./AppDir mkdir -p $APPDIR/usr/lib collect_deps() { local file$1 ldd $file 2/dev/null | grep -E /.*\.so | awk {print $3} | while read -r lib; do case $lib in /lib/*|/usr/lib/*) ;; *) cp -L -n $lib $APPDIR/usr/lib/ || true ;; esac done } collect_deps $APP for lib in $APPDIR/usr/lib/*.so*; do collect_deps $lib done patchelf --set-rpath $ORIGIN/../lib $APPDIR/usr/bin/$APP patchelf --set-rpath $ORIGIN/../../lib $APPDIR/usr/plugins/platforms/libqxcb.so echo done这个脚本的思路是先对可执行文件收集依赖再对所有拷进来的库递归收集可以覆盖大多数漏网之鱼。注意我在cp里加了-n防止重复覆盖加了|| true避免某个权限异常导致整个脚本中断。执行完脚本后我仍然建议手动跑一次ldd抽查几个关键库确保依赖关系完整。另外目标系统上如果还有libGL.so.1、libdbus-1.so.3这类图形和IPC基础库缺失一般建议优先用系统包管理器在目标机器上提前装好而不是把开发机上十几兆的依赖全部塞进包里。因为这类库往往和显卡驱动、系统服务联动擅自打包容易引发更深层的兼容问题。手动打包Qt程序这件事说难不难说简单也不简单。难的地方在于你要理解Linux动态链接的机制不然后面所有报错都像玄学简单的地方在于一旦把流程跑成一套固定的脚本后续每个版本发布就是几分钟的事。我个人实践经验里最有价值的其实是那台干净的目标版本虚拟机每次出包后在它上面双击测试一遍比在开发机上一百次“跑一下没问题”都管用。最后再分享一个小习惯每次打包完成后把ldd输出和readelf -d结果存一份到构建日志里万一未来出现诡异问题翻日志对比往往比重新猜快得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑