简介:这是一份在 Linux 下完成静态编译的 Qt 5.9.9 开发库,编译环境为 CentOS 7.6 x64、GCC 4.8.5、libc 2.17,并启用了 -qt-xcb 图形平台插件。它主要面向需要把 Qt 图形界面程序部署到不带 Qt 运行库的 Linux 目标机、希望以单个可执行文件分发的 C++ 开发者;静态链接后通过 ldd 检查,可确认已无 Qt 相关动态依赖,从而显著降低现场部署时的依赖缺失与版本冲突风险。压缩包整体约 197.4MB,共 2000 个文件,以 .h 头文件、.a 静态库、.prl/.prf/.pri 工程描述文件、.cmake 配置脚本和 .qm 翻译文件为主体,涵盖 Core、Gui、Widgets、Network、Qml 等常用模块,并带有必要的 xcb 平台插件及辅助工具,解压后可直接供 qmake、CMake 或 Qt Creator 工程引用,省去从源码自行编译的冗长过程。该包已有 567 人学习下载;除可直接配置进工程使用的库文件与清晰目录外,也可作为进一步定制 Qt 模块的基线版本,如需自行编译,还可参考作者给出的 configure 静态编译参数。 这个压缩包名字,初看又长又怪,但只要在Linux下折腾过大一点的项目,一眼就能读出里面的信息量:qt-5.9.9-gcc485-libc217-static-qt-xcb.tar.gz。这串字符组合在一起,摆明了是有人(或者某个CI系统)在一个特定的旧环境里,把Qt 5.9.9编译成了静态链接版本,并且特地带上xcb平台插件。它的价值不在于“能跑”,而在于“在老系统上能免掉一堆乱七八糟的依赖直接跑”。如果你正在CentOS 7、RHEL 7这类老系统上做Qt开发,或者手里有一个二进制包需要部署到各种目标机上被xcb插件折磨过,这篇文章就是给你准备的。
我会从文件名拆解开始,逐步讲清楚这个版本的适用范围、编译思路、xcb插件的部署要点,以及我实际踩过和排查过的问题。内容偏实操,不堆概念,希望对正在被Qt部署问题卡住的人有帮助。
1. 文件名拆解:每一个字段都不是白写的
1.1 Qt版本号5.9.9:最后的实用主义者
Qt 5.9.9是5.9 LTS系列的最后一个小版本。5.9本身是Qt比较经典的长期支持版本,官方对它的维护一直持续到2020年。这个版本最大的优势是——对老旧编译器和老旧系统非常友好。它不需要C++14/17的特性,整体构建要求低,而且对glibc的版本要求没有后面5.12、5.15那么激进。
如果你是在一个GCC版本停在4.8.5的环境里做开发,Qt 5.9.9基本就是你能舒服使用的最上限。5.12开始官方虽然还支持GCC 4.8,但实际上很多场景下会遇到配置检测问题,5.15干脆要求GCC 5.3以上。碰到这种环境,与其折腾升级GCC连带影响系统库稳定性,不如老老实实选5.9.9,这也是为什么到现在还能在各大下载站看到这个版本的预编译包。
1.2 gcc485与libc217:系统环境的真实写照
gcc485对应的是GCC 4.8.5,libc217对应glibc 2.17,这两个数字放到一起,几乎可以直接锁定是CentOS 7.x或者RHEL 7.x。这套组合拳意味着你面对的是一个“古老但稳定”的生产环境。老并不可怕,可怕的是这个环境中编译出来的程序,如果动态链接了Qt库,放到稍微新一点的系统上可能因为glibc版本差异跑不起来;反过来,在太新的系统上编译的Qt程序,拿到这里会直接报GLIBC_2.18 not found。
所以这个包的命名方式实际上是在告诉使用者一个关键信息:它是在glibc 2.17下编译的,动态链接时对glibc的依赖被约束到了最低要求。如果你把它拿到CentOS 7、Ubuntu 14.04等老系统上跑,基本不用担心找不到符号的问题。而开发者选择static而不是动态编译,则是更进一步把其余系统依赖的坑也填上。
1.3 static和qt-xcb:一个解决依赖,一个解决显示
static字段表示Qt库本身是静态编译的。意思是你的最终可执行文件会把QtCore、QtGui、QtWidgets这些库直接揉进二进制里,运行时不再需要找libQt5Core.so.5这些文件。
qt-xcb则是平台插件。Qt在Linux下默认通过xcb插件来连接X Window系统,所有基于X11的桌面环境(GNOME、KDE、XFCE等老环境)都得靠它。如果这个插件缺失或不匹配,最常见的报错就是qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in even though it was found,然后窗口直接起不来。把这个字段写进包名,说明编译者专门处理了这个问题,不是随手打了个静态库就完事。
2. 为什么需要静态编译的Qt?搞清这个很多人会想岔
2.1 动态链接的“甜蜜陷阱”
很多人一开始做Linux Qt开发,都是直接apt或者yum装一个qtbase,然后动态编译。开发机上一切正常,等到拷到别的机器上,就开始上演“缺库大作战”。
动态链接的好处是节省磁盘和内存,可以共享库文件,但代价是部署时的依赖地狱。目标机器上少了任何一个libQt5Xxx.so.5,程序就起不来,而且报错往往是分割成好几段出现的——先报缺少libQt5Widgets.so.5,你拷过去,又报缺少libQt5Gui.so.5,再拷过去,又报缺libqxcb.so……这种没完没了的追查过程,我相信每个部署过的人都有心理阴影。
2.2 静态编译解决了什么,不解决什么
静态编译把Qt自身相关的一大堆.so全部揉进可执行文件里,部署的时候你只需要带一个二进制文件,跑起来就会自己把Qt各部分初始化好。这是它最核心的价值——省心。
但要注意,静态编译不是银弹。Qt仍然依赖一些系统级别的第三方库,尤其是xcb插件绕不开X11协议相关的库。比如libxcb、libxcb-xinerama、libxcb-xkb这些扩展库,它不会全部静态包含进去。如果目标系统缺这些,你在启动程序时照样会看到xcb加载失败。所以,静态Qt包在部署时,往往还需要配合一个“系统依赖检查脚本”或者带上相应的.rpm/.deb包一起分发。
2.3 这个组合的实际应用场景
常见于三类需求场景:
- 内网离线环境部署:目标机器不能联网,不能直接从yum源装依赖,只能拷贝一个绿色包直接跑。
- 跨多版本Linux发布:希望同一个二进制在CentOS 7、Ubuntu 14.04、Debian 8等不同发行版上都能运行,动态链接很难做到这一点,而基于老glibc静态编译的包兼容性显著提升。
- 嵌入式或特种终端:系统裁剪过,库不全,又没法随意改系统,只能把应用做到“自带一切”。
3. 从零到一:如何构建这样一个静态Qt包
这部分我更想以“如果你需要自己编译一个完全相同的包”为前提来讲。毕竟,直接拿到现成包固然好,但能搞明白它的来路,遇到问题你才知道往哪儿查。
3.1 环境准备的优先级
编译环境本身最好就是目标环境的上限。比如想兼容CentOS 7,最好就在CentOS 7上编译,这样glibc版本自然就是2.17。如果你的开发机是Ubuntu 20.04,又非要在那上面编,理论上也能通过加一些兼容参数实现,但坑会非常多,不如直接用Docker拉一个centos:7镜像干净。
建议在编译前安装基础依赖:
yum groupinstall "Development Tools" yum install -y libxcb-devel xcb-util-devel xcb-util-image-devel xcb-util-keysyms-devel xcb-util-renderutil-devel xcb-util-wm-devel yum install -y mesa-libGL-devel libX11-devel libXext-devel libXi-devel libXrandr-devel libXfixes-devel fontconfig-devel freetype-devel这些开发包对应运行时目标机器所需要的动态库。其中libxcb-devel尤其关键,因为xcb插件编译时依赖它。缺了它,你后面就算配置了-qt-xcb也编不出完整的插件。
3.2 核心configure参数解析
解压Qt源码后,新建一个build目录以保持源码干净:
mkdir build && cd build ../qt-everywhere-opensource-src-5.9.9/configure \ -static \ -release \ -opensource \ -confirm-license \ -prefix /opt/qt-5.9.9-static \ -qt-xcb \ -qt-xkbcommon \ -no-opengl \ -no-gtk \ -fontconfig \ -system-freetype \ -nomake examples \ -nomake tests这里逐项拆解一下:
-static:编译静态Qt库。-qt-xcb:把xcb相关的库以自带方式编译,而不是完全依赖系统全局版本。这种方式会增加兼容性,避免因为系统xcb库版本差异导致运行时行为不同。-no-opengl:很多老机器没有OpenGL支持,干脆禁用。如果你的应用需要硬件加速,可以改用-opengl desktop,但需要确保目标机器有libGL.so。-fontconfig -system-freetype:使用系统字体配置和后端。Qt界面要正常显示中文,这俩基本是标配,不要在自己实现字体引擎上走弯路。-nomake examples -nomake tests:只编译必要模块,省时间。
configure结束之后,分两步编译:
make -j4 make install如果你的机器内存足够,-j8也行。编译时间大概在30分钟到2小时不等,取决于机器性能。装完之后在/opt/qt-5.9.9-static/lib/下看到的库基本是.a结尾的静态库。
3.3 使用静态Qt编译应用的特殊处理
当你用这个Qt编译自己的项目时,需要额外注意两个“坑中坑”。
第一个是qt.conf文件的处理。即使做了静态编译,Qt在运行时还会找qt.conf来定位插件目录和翻译文件等路径。如果你希望程序在任意目录都能运行,需要在可执行文件旁边放一个qt.conf,里面写:
[Paths] Prefix = . Libraries = . Plugins = plugins这个文件告诉Qt:“我的运行路径就在我所在的位置,不需要去系统路径找。” 如果没有它,有些版本可能会默认去/opt/qt-5.9.9-static找插件,而目标机器上根本没有这个目录,结果就是程序秒退。
第二个是编译时链接参数的顺序问题。静态链接对库顺序非常敏感,必须把更基础的库放在后面,否则会出现“undefined reference”错误。用qmake时可能出现这类问题,解决办法是检查LIBS变量的顺序,通常多试几次就能定位到到底缺的是哪个库。通过pkg-config引入xcb的话,尽量把$(pkg-config --libs xcb xcb-xinerama xcb-icccm xcb-keysyms xcb-xkb)放在最后面,用系统库方式解决顺序冲突。
4. xcb插件的灵魂拷问:为什么静态Qt还是报xcb错误
4.1 问题现象与根因分析
最常见的错误信息长这样:
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.后面往往跟着一行:
Failed to load platform plugin "xcb". Available platforms are: offscreen xcb minimal很多人一看到“static”,就以为程序里已经什么东西都有了,看到这个报错直接从椅子上跳起来:“为什么我都用静态库了还说找不到xcb?” 其实原因很简单:在静态编译模式下,xcb插件是无法在运行时从外部plugins/platforms/libqxcb.so动态加载的——它通常已经被静态编译进去。但xcb插件本身仍然是一个QPluginLoader的产物,它需要在上层补丁中注册成功,而这个注册过程依赖的不是动态库文件,而是Qt自身的Q_IMPORT_PLUGIN(qxcb)这个宏。
如果你是在源码里自己构建Qt的应用,需要在项目的.pro文件或CMakeLists里加入对Q_IMPORT_PLUGIN(qxcb)的引用,或者在main函数之前加入:
#include <QtPlugin> Q_IMPORT_PLUGIN(qxcb)这样编译器才会把静态的qxcb插件符号链接进最终可执行文件。很多基于Qt自定义编译项目的人忘记这一步,就卡在这里。
4.2 运行时依赖库不完全问题
排除插件注册问题后,如果你仍然遇到xcb启动崩溃,则多半是因为xcb插件最终生成的可执行文件仍然动态链接了几个系统级的X11相关库。在一台干净的CentOS 7最小安装上运行时,容易缺的库包括:
libxcb-xinerama.so.0libxcb-xkb.so.1libxcb-icccm.so.4libxcb-keysyms.so.1libxcb-render-util.so.0libxcb-shape.so.0
你可以在编译机器上通过ldd your_app查看依赖,只要看到这类libxcb-*.so出现,就说明它们还是动态依赖。这意味着部署时要么在目标机器上一并安装这些运行时库,要么在编译时利用libxcb的静态版本把这条依赖链也断掉。
但如果要彻底断掉,技术上比较麻烦,因为Qt的-qt-xcb选项通常不足以把整个libxcb全系列静态化。实际项目里更常见的做法是:保留这些运行库的动态依赖,然后在部署包中附上这些.so的副本,或者写一个部署脚本用ldd自动收集依赖并打包。
4.3 使用 ldd 打包依赖的自动化技巧
这里分享一个我常用的部署脚本片段(bash),用于在编译机上提取所有动态依赖:
#!/bin/bash DEPLOY_DIR=./app_pkg mkdir -p $DEPLOY_DIR/plugins cp -a your_app $DEPLOY_DIR/ ldd $DEPLOY_DIR/your_app | grep "=> /" | awk '{print $3}' | xargs -I {} cp -L {} $DEPLOY_DIR/跑完之后,把$DEPLOY_DIR整体拷到目标机器上运行即可。这个方法并不完美,因为某些共享库的依赖可能来自版本不同的路径,但对于大多数xcb相关的系统库,这个做法有效。
5. 常见运行问题与排查技巧实录
这里我按实际项目中遇到的高频问题整理成表,方便查询:
| 症状 | 可能原因 | 处理方式 |
|---|---|---|
| 程序启动后无窗口,终端无输出 | 没有写明qt.conf或插件路径不正确 | 确保可执行文件旁有qt.conf,并设置正确的Plugins路径 |
报libxcb-xinerama.so.0: cannot open shared object file | 目标系统缺xcb拓展库 | 在目标机器上安装libxcb-xinerama,或用ldd收集后一并分发 |
报could not find a Qt platform plugin "xcb"后无法启动 | Q_IMPORT_PLUGIN缺失或静态插件未链接 | 在main中加Q_IMPORT_PLUGIN(qxcb),重新编译 |
报This application failed to start because no Qt platform plugin could be initialized. | 环境变量QT_QPA_PLATFORM设置错误或platform库不匹配 | 检查QT_QPA_PLATFORM,尝试export QT_QPA_PLATFORM=xcb |
| 程序能起但界面上中文字体变方块 | 字体文件缺失或fontconfig配置未生效 | 确认目标机器有中文字体,并安装fontconfig依赖 |
| 使用静态OpenGL相关代码时段错误 | 编译时用了OpenGL但目标环境无GPU支持 | 改用-no-opengl,拉低渲染到CPU路径(或使用QPainter) |
| 程序秒退但要到strace才能看出端倪 | xcb连接失败,通常是远程X服务不允许访问 | export DISPLAY=:0,或检查XAUTHORITY |
5.1 无界面环境下的判断方法
如果程序起的不是GUI版本,只是想测试xcb加载是否成功,可以临时写一个极简demo:
#include <QApplication> #include <QLabel> int main(int argc, char** argv) { QApplication app(argc, argv); QLabel label("hello"); label.show(); return app.exec(); }然后到目标机器上跑。如果demo能弹窗,就说明整套Qt环境没问题,应用本身问题出在别处。如果demo都弹不出来,就用strace跟踪看卡在哪里:
strace -f -e trace=open,openat,execve ./your_app重点看返回ENOENT的文件路径,基本上缺失的库一目了然。
5.2 关于旧的虚拟机和远程X11转发的场景
还有一个常见场景是在Windows上用XShell+Xming或者MobaXterm跑远程Qt程序。这种情况下xcb插件即使没问题,也容易因为X服务对某些扩展特性的支持不足而报xcb_connection_has_error()之类的错误。
如果目标只是远程测试,可以在程序启动前设置:
export QT_X11_NO_MITSHM=1屏蔽特定的共享内存扩展,大部分情况下能绕过去。如果依然不行,就升级X服务端,或者改用VNC方式跑。
5.3 静态包体积和启动速度的取舍
一个静态Qt 5.9.9的最小GUI程序,打包后二进制体积通常在8MB~20MB之间,加上全部插件可能到30MB以上。这比动态链接要多不少。但考虑到免部署带来的无脑体验,这点体积多数场景下是可以接受的。
启动速度方面,静态编译并不必然比动态链接慢。动态链接阶段本身也有成本,反而是静态链接后省去动态加载器解析库符号的时间,在某些环境下肉眼看起来启动更跟手。有些人觉得静态Qt启动慢,多半是因为运行库路径配置有误、Qt在瞎找插件。
写在最后的拉力经验
我自己曾经维护过一套基于CentOS 7 + Qt 5.9.9的工业控制界面程序。最初就是动态编译,每次升级现场版本都要带一堆.so,还经常因为现场机器某个库版本不对导致界面起不来。后来我花了两天时间搭了一个干净环境做静态编译,用的是这个文件名一模一样的版本组合。从那以后部署就变成了拷一个文件往里一放,改改配置文件,系统服务起起来就完事,再没在Qt库依赖上翻过车。
如果你手头已经有这个现成的qt-5.9.9-gcc485-libc217-static-qt-xcb.tar.gz,恭喜你,可以直接跳到第4节和第5节验证自己的应用程序。如果你刚准备折腾这套组合,建议编译之前最好把GCC、libxcb-devel这些环境一次性装齐,毕竟configure失败的配角体验,我替你试过了。
如果再遇到奇奇怪怪的崩溃,优先怀疑的不是Qt,而是系统的X11环境。把DISPLAY变量、Xauthority权限先捋清楚,往往比重新编译快得多。希望这篇东西能帮你少踩几个坑。
本文还有配套的精品资源,点击获取