(正文开始直接以从业者口吻引入)做国产化适配这几年最绕不开的坑就是“源码编译”。明明在标准发行版上十分钟搞定的环境换到麒麟V10上总能给你整出点新花样。今天这篇不聊虚的直接记录一次在纯净麒麟V10环境里从零开始源码编译安装QT5.12.8的完整过程。标题里我特意加了“纯净”两个字什么叫纯净就是这台机器刚装完系统什么都没动过没有现成的编译器、没有装过的开发库、更没有别的团队留下的“历史遗留”配置。这种情况在真实项目里太常见了项目要交付服务器是新拉的生产环境要求物理隔离你必须在最干净的系统上把整个开发环境从零搭起来。很多团队习惯在开发机上编译完直接把部署包带去现场但托管项目、驻场开发项目经常要求现场出包这时候“从零编译”就是必须跨过去的坎。先说结论在麒麟V10上编译QT5.12.8本身没什么黑魔法但需要把依赖、编译器版本、系统库兼容性这三个层面理顺。整个过程耗时按机器配置不同大约1到3小时我踩过的坑包括但不限于xcb依赖缺失、ICU库版本冲突、GLIBC版本不兼容导致的link失败、还有那个经典的“QBasicTimer::start: QBasicTimer can only be used with threads started with QThread”——这些后面都会展开讲。如果你正要在国产系统上搭QT环境或者项目马上要过适配验收这篇实操记录应该能帮你少走不少弯路。1. 编译前准备环境摸底与依赖规划1.1 系统版本硬性确认拿到一台“纯净麒麟V10”第一件事不是急着装东西而是先搞清楚真实环境。麒麟V10根据内核版本和底层发行版来源大体上有两个分支一个基于某老牌Debian系框架一个走的是独立演进路线。虽然桌面版看起来都差不多但包管理器和底层库版本差异很大编译参数和依赖包名可能完全不同。cat /etc/os-release uname -a gcc --version ldd --version | head -n1我这次的实际环境是某版内核4.19的V10GLIBC版本2.28左右。为什么要重视GLIBC版本因为QT5.12.8作为2019年发布的版本其官方二进制包基本都是链接到较新GLIBC上的。如果系统GLIBC太老直接用官方二进制包根本起不来而源码编译则可以绕开这个限制——这是我们的核心优势也是源码编译在一开始就被定为唯一方案的原因。如果你跑完上面的命令发现gcc都没装别慌那正好说明真是纯净环境接下来装编译链和依赖就完事了。1.2 核心依赖清单说明在源码编译QT之前需要把五大类基础依赖装全。缺任何一类configure阶段可能都过不了或者即使过了、编译到某个模块时突然报错回头再排查反而更费时间。我按功能分组整理如下依赖类别核心包用途说明编译工具链gcc, g, make编译源码的基础工具X11基础库libx11-dev, libxext-dev, libxcb*GUI显示的必要底层库图形辅助库libxkbcommon-dev, libxkbcommon-x11-dev键盘事件处理字体与渲染libfontconfig1-dev, libfreetype6-dev字体渲染缺了跑起来全是方块字多媒体与打印libasound2-dev, libcups2-dev音频与打印支持ICU处理libicu-dev统一码符号处理缺了QT会退化为简化模式有一个非常容易踩的坑是麒麟的软件源里对libxcb相关包做了拆包处理常见的包名包括libxcb1-dev和libxcb-util-dev。另外很多教程里会提到libxcb-xinerama0-dev这个包在部分V10源里可能不存在缺失时大概率只会在编译到qtbase源码时以“xcb not found”的形式报出来。sudo apt update sudo apt install -y gcc g make sudo apt install -y libx11-dev libxext-dev libxcb*dev libxkbcommon-dev libxkbcommon-x11-dev sudo apt install -y libfontconfig1-dev libfreetype6-dev sudo apt install -y libasound2-dev libcups2-dev sudo apt install -y libicu-dev1.3 源码获取与校验由于目标环境网络一般很严格推荐在本地开发机先下载好源码包再用U盘等介质拷贝进去。下载时注意两个文件一个是qt-everywhere-src-5.12.8.tar.xz另一个是对应的.md5校验文件。在拷贝进目标机器后执行校验md5sum qt-everywhere-src-5.12.8.tar.xzQT官方每个版本都公布了对应的MD5值以5.12.8为例其发布包校验值在官方下载页可以查得到。这个步骤看似多余但在内网环境里U盘交换介质如果被生产环境的杀软扫过或者在拷贝过程中出了差错会导致一个莫名其妙的问题configure阶段提示文件权限正常但编译到一半出现奇怪的语法错误——那多半是源码包被损坏了。校验通过后解压tar -xvf qt-everywhere-src-5.12.8.tar.xz cd qt-everywhere-src-5.12.82. configure参数选择与底层原理分析2.1 configure的核心逻辑不是越全越好QT的configure脚本是由perl脚本和大量的系统探测逻辑组成的本质上它是一个“能力探测器”逐一检查系统里有没有对应的头文件、库文件、可执行文件然后生成一套qmake.conf和一个最终的qconfig.pri。后者的内容会直接影响编译哪些模块、禁用哪些模块。很多初学者在这里会犯一个明显的错误试图把configure命令写得越长越好觉得自己把所有组件都打开了才叫“完整安装”。实际上在国产系统环境中各模块的交叉依赖非常复杂开启一个模块可能就要求另一个模块存在对应的系统库而这种全局约束在纯净系统中经常不满足。我的原则是只配置当前项目需要的模块其他的一律保持默认或显式关闭。这样做的收益有两个。一是构建时间大幅缩短——QT全量编译在低配服务器上可能要等上三四个小时而按需裁剪基本能压到一小时内。二是减少出错面系统库里没有的内容开着configure反而会让它在编译到某一步时抛出一个奇奇怪怪的链接错误。2.2 实际使用的configure配置参考这次的项目客户的软件是一个安装在国产台式机上的通用桌面应用需要用到Widgets和部分QtNetwork功能暂不需要WebEngine和QML。于是我给出了一套相对收敛的配置./configure -prefix /opt/Qt5.12.8 \ -release \ -opensource \ -confirm-license \ -xcb \ -qt-libpng \ -qt-libjpeg \ -no-opengl \ -skip qtwebengine \ -skip qt3d \ -skip qtcharts \ -skip qtdatavis3d \ -skip qtdoc \ -skip qtgamepad \ -skip qtlocation \ -skip qtmacextras \ -skip qtpurchasing \ -skip qtserialbus \ -skip qtspeech \ -skip qtvirtualkeyboard \ -nomake examples \ -nomake tests我来解释一下几个关键参数-xcb这个参数告诉configure启用XCB平台插件。在麒麟这类基于X11图形栈的系统上这是QT显示的默认途径。如果漏掉它即使编译成功运行时也会报Could not load the Qt platform plugin xcb图形界面根本起不来。-no-opengl麒麟系统的OpenGL环境多数情况下是软件渲染个人电脑或安全终端常屏蔽硬件加速而QT5.12.8默认会尝试找OpenGL头文件。显式关闭可以减少对libGL库和GLX扩展的依赖避免在配置阶段因为缺少libgl1-mesa-dev之类而中断。这里有个观念要扭转过来关闭OpenGL不代表GUI无法显示2D渲染走的是软件光栅化桌面应用完全够用只有在需要3D加速或某些特效场景下才由-no-opengl引发性能限制。-skip系列跳过一大批用不到的组件模块。例如qtdatavis3d是三维数据可视化这个模块编译时间不短而且依赖OpenGL整个去掉可以省心。-nomake examples -nomake tests源码目录里带了大量示例和测试代码默认情况下编译这些纯属浪费时间和磁盘空间。实测这一项能减少约20%的编译时间。2.3 关于ICU与字体库的取舍顺便把配置里没写但必须理解的一件事说透libicu-dev是否安装会影响QT的Unicode与文本排版路径。ICU全称International Components for Unicode它提供了最完整的字符集处理能力QT在检测到系统ICU时会使用GLibICU的组合来渲染复杂文本如果没有ICUQT会回退到自带的简化文本引擎。在中文环境下我的经验是必须装libicu-dev。因为QT自带的无ICU文本布局引擎对CJK字形组合处理方式比较粗糙一些生僻字、注音符号、特殊标点可能会导致布局错乱。尤其是做政务、金融类系统时文本内容经常包含各种冷门字符届时在字体层面排查起来极其痛苦。价格上只是多一个包依赖却可以把潜在渲染雷区彻底铲掉。字体方面libfontconfig1-dev和libfreetype6-dev是Z渲染的中流砥柱。fontconfig负责字体匹配freetype负责字形解析缺任何一个QT跑起来后字体间距异常、中文显示为豆腐块的概率几乎是100%。别问我是怎么知道的问就是那次中文字体乱码排查了三天的经历。3. 编译实操从configure到make install3.1 configure阶段的完整输出解读在源码目录下执行完上面的configure命令后终端会滚动输出一大段检测信息。不要只是盯着最后那行“Qt is now configured for building...”中间的信息量很大尤其要看两个地方一个是 “XCB” 字样附近的状态提示另一个是 “ICU” 附近的版本号。此刻建议执行一次grep来确认关键能力是启用状态./configure ... | tee /tmp/qt_configure.log grep -E XCB|ICU|FontConfig /tmp/qt_configure.log从输出结果里应能看到类似如下行:XCB support ................. yes ICU support ................. yes FontConfig support .......... yes如果看到的颜色是黄色或者no说明对应的系统库没被顺利找到建议先放弃继续编译回头把对应dev包装好。还有那个常见的灰色提示 “QtDBus” 相关的内容如果生产系统上没有强制要求不要强行开启DBus插件否则make阶段会出现对库的链接检查失败。有一个我见过很多人浪费时间的点是configure过程里出现 “No such file or directory” 时大家第一反应是去查环境变量。其实大部分原因并不复杂就是系统里缺少了perl相关的模块。源码目录里的configure脚本是perl写的依赖于libperl-dev或系统已有的perl模块而纯净环境经常不带CGI这个perl核心模块。如果遇到这种报错直接先补一下perl模块再试一次sudo apt install -y perl libperl-dev3.2 make阶段单线程编译的教训在纯净环境里编译QT最常见的抱怨是“太慢了”。默认情况下make采用单线程原因是QT的构建系统为了确保各模块之间的依赖顺序默认串行构建但这在普通机器上往往要跑两小时以上。很多人图快直接执行make -j$(nproc)结果编译到一半内存耗尽或者编译线程相互争抢资源系统直接卡死。在麒麟V10环境下我给一个稳妥的折中方案先用单线程跑一个小的模块热身再切并行。第一步先只构建qtbase这一个模块它是最核心的部件包含QtCore、QtGui、QtWidgetscd qtbase make -j4到这里需要大概10到20分钟具体取决于CPU核心数和内存大小。等qtbase编完并且确认没有报错再退回到顶层目录执行全量编译cd .. make -j4需要注意的是如果你的机器内存小于8GB-j4已经是一个偏激进的选择了。QT5.12的编译每个编译线程大约消耗1.5到2GB的内存4线程就是8GB左右。我实际测过在4GB内存的机器上开-j4编译到QtWebView模块时直接OOM。内存紧张时先用-j2图稳。这里插一个只有实际做国产化项目才懂的点麒麟V10的默认文件系统在某些定制版本里开启了安全增强特性类似强制访问控制当某个进程目录下出现大量新建文件时可能触发审计日志洪泛和变慢。实测纯手动编译时经常遇到make停在某个文件久久不动排查一圈发现根本不是编译卡住而是要访问控制的取签过程非常慢。需要在目标目录上临时放行编译产物的读写权限或者用较低安全策略的模板启动编译环境。各团队的运维方案可能不同但如果你发现自己make指令执行得很“肉”可以往这个方向检查一下。3.3 make install与目录归属编译完成后安装指令吃得很快sudo make install安装日志结尾会列出装好的文件路径和版本信息。默认情况下我们指定的-prefix /opt/Qt5.12.8会让所有库文件和bin目录都落到这个路径下。安装之后还需要处理一个环境配置问题不要让程序直接跑在没有设置环境变量的shell里。最常用的做法是创建一个独立的profile配置而不是改全局/etc/profile。因为我测试过直接在 /etc/profile 里写 QT 的环境变量有概率影响系统自带的其它工具链。更推荐把配置写到一个单独文件sudo vim /etc/profile.d/qt5128.sh写入下面三行export PATH/opt/Qt5.12.8/bin:$PATH export LD_LIBRARY_PATH/opt/Qt5.12.8/lib:$LD_LIBRARY_PATH export QT_PLUGIN_PATH/opt/Qt5.12.8/plugins执行source /etc/profile.d/qt5128.sh然后运行qmake -v验证是否指向5.12.8qmake -v预期输出是QMake version 3.1 Using Qt version 5.12.8 in /opt/Qt5.12.8/lib如果这里显示的版本不是你编译的这个比如冒出来的是系统自带的另外一个QT版本注意检查PATH的优先级顺序。这就是为什么我坚持在profile.d单独写文件而不是直接改装在PATH前面追加一串的原因——干净可回滚不影响系统其它组件。4. 运行时常见问题定位与解决实录4.1 平台插件加载失败xcb错误这是新建QT环境时出现频率最高的报错没有任何悬念。报错信息通常长这样This application failed to start because it could not load or find the Qt platform plugin xcb in .出现这种情况时很多人的第一反应是把QT_PLUGIN_PATH配好但配好之后仍报同样错误的情况也特别多。这时就要用QT_DEBUG_PLUGINS1拉起程序它会打印每个插件的加载细节包括找不到的具体动态库名称QT_DEBUG_PLUGINS1 ./你的程序根据我的经验最常见的缺失库是libxcb-xinerama.so。虽然我们configure时指定了xcb但xinerama扩展是否在编译期启用取决于当时系统里有没有libxcb-xinerama-dev。如果没装QT会在运行时尝试加载libxcb-xinerama.so.0然后抛出上述错误。解决办法有两个sudo apt install -y libxcb-xinerama0装完后再次运行程序大概率就好了。还有一个备用技巧如果你不方便安装新包可以用LD_PRELOAD预先挂载缺失的库但这个办法只能是临时应急真正部署时还是要保证库存在齐全。4.2 GLIBC版本冲突同一二进制在两台机器上的表现不同源码编译最大的好处其实是规避这个问题但如果你从一台编译机器上把生成的可执行程序直接拷贝到另一台机器上运行还是可能遇到/lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.29 not found这是因为编译机器上的headers比较新编译时对某些符号自动选择了新版本的glibc绑定。解决办法有两个方向。第一如果目标机器GPU型号一致、处理器也一致优先把编译工作放到目标机器上做从源头杜绝版本漂移。第二如果搞不定现场编译就要在编译机上特殊处理设置-ffunction-sections -fdata-sections以及-Wl,--gc-sections来裁剪未使用的符号尽可能减少对新GLIBC符号的引用。说句实话我在实际项目里更推荐第一种方案现场编译。反正QT5.12.8的源码包也就1GB左右用一个U盘就能带过去而现场编译一次1小时的工作量比调试glibc兼容性问题省太多精力了。4.3 运行时中文乱码与字体问题