如果你现在手里正好有一台某国产Linux发行版V4的机器,想把Qt升级到5.12.12,翻了一圈发现系统源里只有一个版本很老、而且明显不是为开发准备的Qt包,Qt官方在线安装器在这套系统上又跑不起来,那么你大概率也会走到我上个月走过的路:下载源码包,自己动手编译。
这篇东西就是完整的操作记录。从最开始的“为什么要自己编”,到环境摸底、configure参数选择、make过程的意外处理,再到装完之后的部署和发布配置,每一步我都把当时的思路、踩到的坑和最终的处理方式写清楚了。目标读者是那些需要在国产Linux环境里做桌面或嵌入式开发的人,尤其是第一次在这种系统上碰Qt源码编译、又不太想走弯路的同学。
1. 为什么某系统V4上需要自己编译Qt源码,而不是等源里的现成包
1.1 系统源里的Qt包,往往不是给开发者准备的
某系统V4这类国产Linux发行版,本身的定位是稳定的基础环境,所以自带软件源里的Qt包更多是给系统组件自己用的。我在那台机器上搜包的时候,源里确实有Qt5相关的东西,但要么版本偏低,要么拆得七零八落,缺了开发头文件,更别提QML、WebEngine这类功能模块。
就算勉强把缺的包一个个补齐,版本之间还可能互相打架。某系统V4的包仓库和上游开源生态不是完全同步的,某些库的版本被锁死在固定版本上,你没法用一个明确的Qt小版本来保证API行为一致。对于要做长期维护的项目来说,这种“不确定版本”本身就是风险。
自己编译源码,至少能拿到一个完全可控的Qt环境:版本固定、模块按需裁剪、编译参数自己指定。后面不管是产线部署还是开发机迁移,只要把同一个部署目录拷过去,环境就能复现。
1.2 为什么选5.12.12,而不是5.15或6.x
关于版本选择,我需要多说两句。某系统V4上常见的编译器是GCC 7到9之间的版本,如果直接上Qt 6,它会要求C++17,并且整套构建体系换成了CMake,依赖链也变长,在这类平台的兼容性风险其实挺大。5.15虽然仍在支持期,但它的小版本发布节奏和补丁分支在国产平台上适配程度不一,我也见过有人在某系统上编5.15出现xcb插件问题的案例。
5.12.12是5.12这个系列的最后一个小版本,整个5.12分支以稳定著称,而且对老编译器、老系统库的容忍度很高。它的源码包编译方式仍然是熟悉的qmake那套,不要求CMake,很多内网环境里没有额外工具链也能编。对我这个场景来说,5.12.12是“最能一次编过”的选项,这句话在国产系统上权重很高。
1.3 动态编译为主,静态编译先放一边
这里先交代一个选型结论:我用的是动态编译。Qt在Linux平台下默认就是动态库方式,configure不加特殊参数就会生成一大堆so文件,应用程序运行时通过rpath和LD_LIBRARY_PATH找到它们。
我之所以没有考虑静态Qt,是因为静态编译要求所有依赖库都有静态版本,某系统V4上X11、xkbcommon、icu这些库不一定都带.a文件,就算带了,还要处理许可证和打包体积的问题。动态编译配合部署目录拷贝,反而是国产系统发布软件最常用的方式。
2. 动手前先摸清这台机器的家底
2.1 工具链版本核查:gcc、make、glibc
编译Qt之前,我建议你先花半小时把系统基础环境搞清楚,省得configure跑到一半才发现编译器太老,或者某个系统库根本不对。
在终端里依次执行:
gcc --version g++ --version make --version ldd --version | head -1 cmake --version某系统V4自带的gcc版本如果是7.x或8.x,编Qt 5.12.12完全没有压力,5.12官方要求C++11,这个门槛很低。真正需要关心的是glibc版本,因为Qt编译出来的二进制运行时会动态链接glibc,如果部署目标机器比开发机的glibc还老,会有兼容性问题。我的原则是:开发机和发布目标机尽量保持同一套某系统V4的小版本,别跨大版本发布。
还要确认make版本别太老,4.x都行,老版本可能出现并行编译时文件依赖解析不完整的问题。
2.2 磁盘、内存和swap的硬性预算
Qt源码包解压后大概2GB到3GB,完整编译5.12全家桶的话,中间产物加上最终部署目录,轻轻松松超过15GB。我还见过有人只编译基础模块加qtwebengine,最后磁盘占用超过35GB的情况。所以第一件事就是检查磁盘剩余空间:
df -h /home free -h如果可用空间低于30GB,我劝你先清垃圾或者换大分区,不要在这个环节省事。内存方面,编译Qt的链接阶段特别吃内存,8GB勉强够用但会卡,16GB算是舒适。如果机器只有8GB内存,建议编译前手动加swap,别让系统在链接QtWebEngine或者QML模块时直接OOM(内存耗尽)。
临时swap的加法和清理方式:
sudo fallocate -l 8G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile编译完了如果不需要,直接sudo swapoff /swapfile再删文件就行。
2.3 最容易漏的依赖包清单:xcb、xkbcommon、fontconfig、icu
这是整个编译过程中最值得花時間的一步。Qt在Linux上跑图形界面,靠的是XCB插件,而XCB插件编译时又依赖一大堆xcb-*库。configure会检测这些依赖,检测失败并不会直接终止,它只是默默把当前系统的xcb特性关掉,最后你make install完一运行程序,Qt告诉你“could not find the Qt platform plugin xcb”——那时候排查起来才真要命。
我在某系统V4上提前安装的依赖包大概包括这些,直接用系统的包管理工具装上:
sudo apt install -y build-essential perl python3 git sudo apt install -y libgl1-mesa-dev libglu1-mesa-dev sudo apt install -y libxcb1-dev libxcb-icccm4-dev libxcb-image0-dev sudo apt install -y libxcb-keysyms1-dev libxcb-render-util0-dev sudo apt install -y libxcb-shm0-dev libxcb-xinerama0-dev sudo apt install -y libxcb-cursor0-dev sudo apt install -y libxkbcommon-dev libxkbcommon-x11-dev sudo apt install -y libfontconfig1-dev libfreetype6-dev sudo apt install -y libicu-dev libudev-dev libts-dev如果你是直接面对一台安装阶段没接图形界面的机器,建议把上面的包一次性装齐。许多教程会漏掉libxkbcommon-x11-dev和libxcb-cursor0-dev,这两个不装,configure阶段报错不太明显,但会在make阶段卡在xcb相关源文件的编译上。
关于包管理器的说明:某系统V4不同细分版本用的包管理工具不一样,有的是apt系,有的是rpm系。我上面写的是apt命令,如果是rpm系环境,包名大多数类似libxcb-devel、libxkbcommon-devel这种带-devel后缀的形式,对应关系一目了然。
2.4 OpenGL和GPU现状评估:这决定了后面图形平台插件的走向
某系统V4常见的是集成显卡或者老一代独立显卡,驱动情况参差不齐。所以编译前一定要确认OpenGL到底可不可用。
我当时的判断方式分三步:
- 看内核加载的显卡驱动:
lsmod | grep -E "nouveau|i915|amdgpu"; - 确认Mesa开发库已装:
dpkg -l | grep libgl1-mesa-dev; - 简单跑一个
glxinfo或者glxgears测试图形环境是否正常。
如果这些命令一个都不存在,或者系统根本没有图形界面,那就得考虑Qt能不能在无GPU环境里跑。解决办法是configure时加-no-opengl或者-opengl es2,然后依赖linuxfb或者eglfs这类平台插件。
我在实际项目里用的是带xcb加桌面OpenGL的方式,因为某系统V4多数情况还是当作桌面工作站来用,接显示器跑Qt客户端程序,xcb是最稳妥的窗口系统插件。如果你是要在无显示器的工控盒子上跑,那就走-linuxfb的路线,两者不要弄混。
3. configure配置环节:别全要,按需打开
3.1 基础配置:授权、版本和安装路径
源码解压完成后,进入目录,先看一眼顶层哪个目录下面有configure文件。Qt 5.12.12的源码包通常叫qt-everywhere-src-5.12.12.tar.xz,解压命令:
tar -xf qt-everywhere-src-5.12.12.tar.xz cd qt-everywhere-src-5.12.12configure是编译的第一步,它会把接下来所有模块的Makefile生成好。最基础、也最关键的三件事是授权确认、构建模式、安装前缀。
./configure -prefix /opt/qt-5.12.12 -opensource -confirm-license -release-prefix决定了最终make install的部署目录。我强烈建议放在/opt/qt-5.12.12这种固定的、不带版本大跨度变动的路径,而不是默认的/usr/local。这样以后想删掉整套Qt,直接删这个目录,不会污染系统其他软件。
-opensource加-confirm-license是提前把开源协议确认这件事做掉,免得configure过程中弹交互提示,在某些无人值守环境里卡住。-release表示编Release版本,如果要调试Qt自身再去考虑-debug,一般开发不需要。
3.2 图形平台插件:xcb、xkbcommon、linuxfb选哪个
这个阶段的图形插件选择,决定了编译出来的Qt能跑在什么显示环境里。
如果你要跑带窗体的桌面程序,必须启用xcb基础支持。configure参数里会有对应的特性开关,我用了:
-xcb -xkbcommon-xcb是启用X Window System下的Qt平台插件。-xkbcommon则是键盘输入管理,Qt 5.0之后的版本基本都依赖它,没这个库会导致按键事件错乱或者xcb插件直接加载失败。
如果你的目标环境没有显示器,或者只有一块没有X服务的开发板,那就需要加上-linuxfb。linuxfb是直接操作Linux framebuffer设备的平台插件,不需要X11,但代价是输入设备得自己处理,窗口管理功能也几乎没有。
我更建议的条件是:拿不准环境有没有X服务、显卡驱动能否正常工作时,全部编进去。Qt的configure允许你同时启用多个平台插件,运行时通过-platform参数选择,这样一套部署包走到哪儿都能用:
-xcb -xkbcommon -linuxfb -eglfs3.3 模块裁剪:把qtwebengine这类重货排除掉
Qt 5.12.12整个仓库包含的模块很多,qtwebengine(基于Chromium的那套浏览器内核)、qtwebview、qtcharts这些模块,各有各的重型依赖。
我在某系统V4上编译时,直接跳过qtwebengine。原因很简单:第一,Chromium的编译需要额外的Python、Ninja,以及大量内存,在国产老机器上很容易失败;第二,我的应用场景根本不需要内嵌浏览器。为了一个用不到的模块把整个编译拖垮,不值得。
模块裁剪的参数是-skip,可以重复指定多个模块:
-skip qtwebengine \ -skip qtwebview \ -skip qtcharts \ -skip qtdatavis3d \ -skip qtgamepadqtcharts、qtdatavis3d这类可视化模块,如果项目里的图表需求没有,也可以减掉。这样configure阶段生成的Makefile少,make阶段也省时间。
关于-ICU这个特性,我建议别图省事直接-no-icu。Qt的文字处理、本地化有些环节需要ICU,某系统V4上缺icu库时configure也能过,但部分QPA插件和Qt GUI功能会被削弱。所以前面依赖包里我专门列了libicu-dev,这种情况下默认的icu检测会直接通过,不需要额外加参数。
3.4 一份可以直接抄走的configure命令
综合上面的考虑,我在那台某系统V4机器上最终执行的配置命令是这样:
./configure -prefix /opt/qt-5.12.12 \ -opensource -confirm-license \ -release \ -xcb -xkbcommon -linuxfb \ -fontconfig \ -skip qtwebengine \ -skip qtwebview \ -skip qtcharts \ -skip qtdatavis3d \ -nomake examples \ -nomake tests \ -no-feature-vnc \ -recheck-all-nomake examples和-nomake tests是避免make阶段把示例和测试代码全部编译一遍,既能省时间,又能减少磁盘占用。如果不是为了跑Qt自带的demo,这两项建议都关掉。
-recheck-all是我兜底用的,它会强制重新执行所有依赖检测,避免之前某次失败的检测结果被缓存影响后续判断。
configure执行完,屏幕上会列出一大堆feature开关的状态,我专门关注这几行的输出:
xcb ........................ yes xkbcommon .................. yes xcb-xlib ................... yes fontconfig ................. yes icu ........................ yes只要这些是yes,就说明系统依赖基本齐了。如果哪个显示no,回到第2.3节去装对应包,然后重跑configure,不要硬着头皮make。
4. make阶段:耗时、资源与现场排障
4.1 并行编译到底开几路
configure通过之后,make阶段就是漫长的等待。很多人习惯无脑make -j$(nproc),把CPU核心数全部压上去,这在内存充足的机器上是没问题的,但在某系统V4这种老平台上很容易把内存打爆。
我当时那台机器是16核,但内存只有16GB,一开始直接make -j16,跑到一半系统卡顿明显,后来换成make -j6才稳定。现在的经验是:内存小于等于16GB,最多-j4或者-j6;32GB内存可以考虑-j8;再多核心就别指望并行度拯救了,链接阶段依然是瓶颈。
稳妥的命令是:
make -j4如果机器确实能扛,再加到-j8,观察负载和内存占用后再决定。编译Qt这种体积的代码,一次生产环境构建磨一两个小时很正常,中间不需要一直盯着,但最好在终端里开着htop或者free -h,看内存是否吃紧。
4.2 configure阶段的假成功和真实错误
我这次编译过程中,最让人头疼的不是make报错,而是configure阶段一切正常,make阶段才炸出来一堆“缺头文件”的问题。原因就是我前面提到过的:configure对部分非核心特性采用“检测不到就禁用”的宽容策略,不会直接报错终止。
举个例子:某次我重新换了一台某系统V4机器,configure输出里xcb是yes,但make时编译qxcbxkbcommon.cpp直接报xkbcommon/xkbcommon.h: No such file or directory。我回头一看,那台机器只装了libxkbcommon-dev,没有装libxkbcommon-x11-dev,configure因为x11相关的子检测没跑全,就误判了。
所以make报错时,别急着恍惚,先读错误里的文件名。凡是xxx.h: No such file或者cannot find -lxxx这种错误,都是依赖缺失的问题,定位到对应的开发包安装即可。
4.3 cc1plus被Killed之后怎么办
链接大模块时最容易看到这样一条错误,尤其是编译QML模块或者GCC插件相关代码时:
cc1plus: fatal error: Killed signal terminated program cc1plus这个错误基本等同于内存耗尽。系统把正在编译的cc1plus进程干掉了,不是你代码问题,也不是Qt源码问题,就是没内存了。
处理方案有两个:要么调低并行度,把make -j4换成make -j2;要么按第2.2节的方式加swap。我当时是两者一起做,先加上8GB swap,再把并行度降到4,问题消失。
另外注意一个细节:make -j的并行度要基于链接阶段的内存占用,而不是编译阶段。编译阶段每个编译器进程占几百MB,但链接QtGui、QtWebEngine这类超大二进制时,单个链接器进程能占到2GB以上,所以最保险的做法是make时不离开,盯着内存曲线,掉头就跑不下去时立刻降到-j2。
4.4 make进程跑到一半被人为中断的处理
如果编译途中你按了Ctrl+C,或者机器意外断电,不需要重新解压源码包。Qt的构建系统是基于Makefile的,中断后重新执行同样的make命令,它会自动跳过已经完成的目标,从断点继续。
但有一个前提:configure生成的Makefile缓存不能删。不要手贱去清空源码目录或者删.qmake.stash这类文件。我当时有一次为了“干净重来”,把整个build目录删了重跑configure,白白浪费一个多小时。
如果只是想重新配置,就执行:
make distclean ./configure ...这才是正规的“重新来过”流程。
5. make install做完,并不意味着收工
5.1 设置PATH和LD_LIBRARY_PATH的正确姿势
make阶段结束后,执行安装:
sudo make install装完之后,Qt被放到了/opt/qt-5.12.12。此时你输入qmake -v大概率还是找不到命令,因为系统PATH里没有这个目录。
我建议在/etc/profile.d/下面创建一个qt5.12.12.sh文件,写入:
export QTDIR=/opt/qt-5.12.12 export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH export QT_PLUGIN_PATH=$QTDIR/plugins然后source /etc/profile.d/qt5.12.12.sh重新加载,这时候qmake -v就能正常输出版本号。
这里有一个容易踩的坑:如果你同时用着系统自带的Qt库,LD_LIBRARY_PATH这么一放,会把所有程序的Qt库都指向我们刚编译的5.12.12。某个系统组件如果依赖旧Qt,可能会因此出问题。我在实际使用中就是直接接受这个影响,因为这台机器是专用的开发机,不跑其他图形业务程序。如果你的机器有更多用途,建议把环境变量放在项目级脚本里,而不是全局生效。
5.2 用Qt Creator接入自编译Qt工具链
某系统V4上经常有人直接安装Qt Creator,但它自带的Qt版本和工具链可能不是你编译的这套。打开Qt Creator后,菜单路径是“工具”->“选项”->“Kits”->“Qt Versions”,点击新增按钮,选择/opt/qt-5.12.12/bin/qmake,编译器选择系统自带的gcc。
接着在“Kits”标签页新建或修改默认套件,把Qt Version指向刚添加的版本。这样新建项目时,构建和运行都会走你自己编译的Qt环境。
我见过有人在Qt Creator外面改了一堆环境变量,结果Creator内部一直找不到qmake,搞得晕头转向。正确顺序是:先确认终端里qmake -v正常,再在Creator里配置,因为Creator本质上也是调用qmake和make,它在哪个环境下能跑,配置界面就会给你相同的结果。
5.3 qmake自检与最小Demo验证
编完Qt的第一个问题永远是:真的能用吗?
我自己习惯写一个最小的Qt Widgets程序能跑起来才算数。建一个临时目录,里面两个文件:
test.pro:
QT += widgets SOURCES += main.cpp TARGET = qtcheckmain.cpp:
#include <QApplication> #include <QLabel> int main(int argc, char *argv[]) { QApplication app(argc, argv); QLabel label("Qt 5.12.12 build ok"); label.show(); return app.exec(); }然后执行:
/opt/qt-5.12.12/bin/qmake test.pro make ./qtcheck如果弹出一个窗口,说明xcb插件、OpenGL检测、系统字体环境全部正常。如果窗口没弹出来,终端提示could not load platform plugin "xcb",那就是第6.2节要讲的问题,先把库路径排查一遍。
5.4 发布程序时要带走的Qt运行文件
很多人编完Qt后第一个程序确实能跑了,但它是在开发机上跑的,因为依赖了/opt/qt-5.12.12/lib下面的动态库。部署到另一台某系统V4机器上时,要么装一遍同样的Qt,要么把运行文件带过去。
我惯用的发布方式是打一个运行目录。假设可执行文件叫myapp,目录结构长这样:
myapp/ ├── myapp └── lib/ ├── libQt5Core.so.5 ├── libQt5Gui.so.5 ├── libQt5Widgets.so.5 ├── libQt5XcbQpa.so.5 ├── libQt5DBus.so.5 └── platforms/ └── libqxcb.soplatforms目录至关重要,Qt通过插件机制加载窗口平台,它不会自动去/opt/qt-5.12.12/plugins/platforms找,除非设置了QT_PLUGIN_PATH。所以发布时一定要把整个plugins/platforms目录拷到程序旁边。
运行脚本里显式设置库路径:
#!/bin/sh export LD_LIBRARY_PATH=/opt/myapp/lib:$LD_LIBRARY_PATH export QT_QPA_PLATFORM_PLUGIN_PATH=/opt/myapp/lib/platforms ./myappQT_QPA_PLATFORM_PLUGIN_PATH是Qt查找平台插件的第一优先级位置,显式指定后不会找错。
6. 那些重复出现的坑,我最后是怎么规避的
6.1 写一个一键编译脚本,把configure记录固化下来
编译Qt这种流程,如果只在终端里敲一遍,下次换台机器就得从头回忆参数,非常痛苦。我在第一次编译成功后就把整个流程固化成脚本,放在源码目录外面:
#!/bin/bash export QTDIR=/opt/qt-5.12.12 export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH cd qt-everywhere-src-5.12.12 ./configure -prefix /opt/qt-5.12.12 \ -opensource -confirm-license \ -release \ -xcb -xkbcommon -linuxfb \ -fontconfig \ -skip qtwebengine \ -skip qtwebview \ -skip qtcharts \ -skip qtdatavis3d \ -nomake examples \ -nomake tests \ -recheck-all make -j4 sudo make install以后换机器,脚本改一改路径,无脑执行。另外configure完成后生成的config.log和config.summary不要删除,里面记录了完整的检测结果,排查问题时比看Makefile图谱高效得多。
6.2 xcb库版本冲突导致的“could not load platform plugin xcb”
这是国产系统上出现频率最高的Qt运行时报错,即使在编译阶段全部通过,运行阶段也可能炸出来。
我遇到的具体现象是:程序启动时提示:
This application failed to start because no Qt platform plugin could be initialized. Available platform plugins are: eglfs, linuxfb, minimal, offscreen, xcb.排查链路分三步:
- 确认
platforms/libqxcb.so存在,不在就别谈; - 用
ldd libqxcb.so | grep "not found"检查它依赖的动态库是否有缺失; - 用
LD_DEBUG=libs ./myapp查看程序启动时实际加载了哪些Qt库路径,确认是不是混进了系统自带的Qt库版本。
xcb插件加载失败,十有八九是第3步出了问题:程序先加载了系统里旧版本的libQt5Core或者libxcb,跟/opt/qt-5.12.12下刚编译的库冲突了。这种情况下的方案是把LD_LIBRARY_PATH清空,让程序通过rpath走自己的目录,或者在启动脚本里调整库搜索顺序,把自己编译的目录放在最前面。
6.3 关于这套Qt的生命周期维护心得
最后说点编译之外的体会。自己编译Qt源码这件事,看起来是个一次性工作,实际上等于给自己维护了一条“软件供应链”。某系统V4的仓库里可能不会同步更新Qt补丁,但只要源码包还在,后续打补丁、改编译参数、更换架构重编,都在可控范围内。
我后来在另一个项目里要适配不同CPU平台,重新编译同一套5.12.12源码,只需要把configure参数里的相关架构选项调整一下,其他流程基本照搬。这种可控性,比从第三方源装二进制包踏实得多。
如果你恰好也在类似场景里,从今天开始自己编译Qt,把关键步骤记下来,你大概率会发现:这条路走一次之后,以后所有环境适配问题都不再是问题。