☰
解决buildroot交叉编译Qt qmake Unknown module错误
2026/10/3 13:10:09 网站建设 项目流程

先说一句,这个问题我真的见了不知道多少次:板子上用的 buildroot,Qt 环境是交叉编译的,写了个 .pro 文件,里面加了QT += serialport或者QT += charts,然后在宿主机上执行qmake && make,结果 qmake 阶段直接给你来一句:

Project ERROR: Unknown module(s) in QT: serialport

然后你就开始怀疑人生——明明宿主机 Qt 环境里用得好好的,怎么一交叉编译就翻车?更诡异的是,有些模块在宿主机上装了,buildroot 的 sysroot 里也有对应的 .so,但 qmake 还是说不认识。

这篇文章就把这个问题彻底拆开,从 qmake 判断模块的机制讲起,到五步定位法,再给 buildroot 里的完整操作流程,最后把上板运行时的配套坑也一并端上来。用的板子是全志 T113 这类常见 ARM 平台,但思路对所有 buildroot + Qt 的嵌入式环境都通用。

1. 先搞清楚 qmake 判断“这个模块存在”的依据

1.1 qmake 不是看文件,而是查“词典”

很多人的第一反应是去 sysroot 里搜,比如:

find output/staging/usr/lib -name "*Qt5SerialPort*"

搜出来确实有libQt5SerialPort.so,然后就很困惑:库都在,你 qmake 凭啥说 Unknown module?

这里要理解一个关键点:qmake 在解析QT += serialport时,并不是去文件系统里找有没有libQt5SerialPort.so,而是去查自己的一组“模块记录文件”。你可以把这堆记录文件理解成一本词典,qmake 查词典里有没有serialport这个词条。词条里有库名、头文件路径、依赖关系这些信息。查到了,才认为模块可用,才会在后续生成 Makefile 时加上-lQt5SerialPort和头文件路径;查不到,直接报 Unknown module(s)。

这套记录文件在 Qt5 里的表现形态主要是:

  • 模块安装时生成的.prl文件,一般在<prefix>/lib/下,比如libQt5SerialPort.prl;
  • 对应的 pkg-config 描述文件,一般在<prefix>/lib/pkgconfig/下,比如Qt5SerialPort.pc;
  • 一部分模块信息在<prefix>/mkspecs/modules/下的.pri文件里。

qmake 编译时会把QT_INSTALL_PREFIX这些路径硬编码进去。PC 上你装 Qt,安装器会把这些“词条”全部写全;buildroot 环境里则完全不一样。

1.2 buildroot 的 Qt 是被拆成一堆独立包的

在 buildroot 里,qt5base只是基础包,包括 Core、Gui、Widgets、Network 这些最常用的模块。serialport、charts、mqtt、multimedia、websockets、webengine这些统统是独立包,每个包都有自己的 Kconfig 开关。

比如:

  • QT += serialport,对应BR2_PACKAGE_QT5SERIALPORT
  • QT += charts,对应BR2_PACKAGE_QT5CHARTS
  • QT += mqtt,对应BR2_PACKAGE_QT5MQTT

如果对应的 Kconfig 没有勾上,buildroot 根本不会去编译这个模块,那么 sysroot 里连“词典”都没有——虽然可能残留了一些 .so 文件(比如你手动拷进去的),但 qmake 的记录文件不存在,照样报 Unknown module(s)。

换句话说,这个报错的直接原因基本都是同一句话:当前这个 qmake 的模块记录文件里,没有你要用的那个模块。要么是没使能,要么是使能了但没重新编译进去,要么是你用错了 qmake。

1.3 宿主机和 buildroot 的 qmake 混用是最常见的翻车现场

我自己遇到过的案例里,至少有一半是这条:用户用 buildroot 编译了工具链,if 他们的 PATH 里没有 buildroot 的 qmake 路径,Qt Creator 或者终端里which qmake指到了宿主机的/usr/bin/qmake。宿主机 qmake 的词典里也许有 serialport,但那是宿主机的 Qt,不是你目标板的 Qt。于是出现两种情况:

一是宿主机 Qt 版本和目标板 Qt 版本不一致,宿主机上这个词条恰好没有,报错;二是宿主机 qmake 能找到模块,编译也过了,但生成的是 x86 二进制,链接的是宿主机库,拷到 ARM 板子上直接段错误或者“cannot open shared object file”。

所以遇到这个报错,先别急着去 buildroot 里加包,第一件事是确认你正在用的 qmake 到底是谁。

2. 动手定位:五步确认是“没使能模块”还是“用错了 qmake”

2.1 第一步:用 buildroot 自带的方式拿到 qmake

假设你的 buildroot 在/home/xxx/buildroot,目标板配置已经编好。打开终端,先确认 buildroot 有没有生成 qmake 工具:

make qmake

这个 target 会触发 buildroot 内部的 Qt qmake 工具准备,执行完以后,qmake 的路径一般在:

output/host/bin/qmake

建议直接查看这个路径是否存在,并且看一下版本:

ls -l output/host/bin/qmake output/host/bin/qmake -v

如果显示的是类似:

QMake version 3.1 Using Qt version 5.15.2 in /home/xxx/buildroot/output/staging/usr/lib

那说明这个 qmake 是 buildroot 内部的,且它认可的 Qt 前缀是 staging 目录,逻辑正确。如果提示“command not found”,说明你压根没编 Qt,回 buildroot 里先确认BR2_PACKAGE_QT5BASE有没有勾选。

2.2 第二步:用 qmake -query 解剖路径

这一步特别有用,它能直接暴露 qmake 认的安装路径和系统 qmake 认的路径差异:

output/host/bin/qmake -query

重点看这几个值:

QT_INSTALL_PREFIX:/home/xxx/buildroot/output/staging/usr QT_INSTALL_LIBS:/home/xxx/buildroot/output/staging/usr/lib QT_INSTALL_HEADERS:/home/xxx/buildroot/output/staging/usr/include/qt5 QT_INSTALL_ARCHDATA:/home/xxx/buildroot/output/staging/usr/lib/qt5

如果QT_INSTALL_PREFIX是/usr这种,说明你执行的其实是宿主机的 qmake,不是 buildroot 的。如果前缀是 buildroot 的 staging 路径,名词条就在这个路径下面找。

2.3 第三步:在 sysroot 里搜模块的“词典”

确认 qmake 前缀没问题后,去 staging 里搜你想要的那个模块,是否带上了 qmake 能识别的记录文件。

比如查 serialport:

find output/staging/usr/lib -name "*Qt5SerialPort*" find output/staging/usr/mkspecs/modules -name "*serialport*"

正常情况下应该看到类似:

output/staging/usr/lib/libQt5SerialPort.so output/staging/usr/lib/libQt5SerialPort.so.5 output/staging/usr/lib/libQt5SerialPort.so.5.15.2 output/staging/usr/lib/libQt5SerialPort.prl output/staging/usr/lib/pkgconfig/Qt5SerialPort.pc output/staging/usr/mkspecs/modules/qt_lib_serialport.pri

其中.prl、.pc、.pri就是 qmake 查的“词典”;如果只有 .so,没有 .prl 和 .pri,qmake 照样不认识。

2.4 第四步:查 buildroot 的 .config 确认模块开关

直接看配置:

grep -i serialport .config

如果输出有:

BR2_PACKAGE_QT5SERIALPORT=y

说明 buildroot 配置里已经开了这个模块。如果 grep 不到,或者显示# BR2_PACKAGE_QT5SERIALPORT is not set,那就是模块压根没使能。到这里,报错的直接原因基本就锁定了。

2.5 第五步:用一个小 .pro 让 qmake 自己告诉你答案

为了快速确认,你可以建一个临时目录,写一个只有几行的 .pro 文件:

QT += core greaterThan(QT_MAJOR_VERSION, 4): QT += widgets qtHaveModule(serialport) { message("serialport module is available") } else { error("serialport module is NOT available") }

然后:

output/host/bin/qmake

如果 qmake 还报 Unknown module(s) in QT: serialport,说明词条完全没有;如果报 “module is NOT available”,说明词条不完整或模块编译有问题。如果直接打出 “serialport module is available”,那 .pro 里加QT += serialport之后编译就不该报这个错——除非你的工程文件里写错了模块名。

3. buildroot 里真正让模块“可用”的完整操作流程

3.1 menuconfig 里使能 Qt 模块

前面的定位如果确认是“没使能模块”,那就回到 buildroot 根目录:

make menuconfig

进入路径:

Target packages → Graphic libraries and applications (graphic/text) → Qt5

在 Qt5 的菜单树里找到你需要的模块。命名一般很清楚,比如qt5serialport、qt5charts、qt5mqtt、qt5multimedia、qt5websockets等。用空格键把它勾上。

这里给一张我常用的映射表,方便你对照:

.pro 里写的模块名buildroot 菜单/Kconfig 选项说明
QT += serialportBR2_PACKAGE_QT5SERIALPORT串口通信模块,嵌入式必备
QT += chartsBR2_PACKAGE_QT5CHARTS图表模块,工业 HMI 常用
QT += mqttBR2_PACKAGE_QT5MQTTMQTT 通信模块
QT += multimediaBR2_PACKAGE_QT5MULTIMEDIA多媒体模块,依赖较多
QT += websocketsBR2_PACKAGE_QT5WEBSOCKETSWebSocket 客户端/服务端
QT += scriptBR2_PACKAGE_QT5SCRIPTQt Script 模块
QT += xmlpatternsBR2_PACKAGE_QT5XMLPATTERNSXML 模式匹配模块
QT += locationBR2_PACKAGE_QT5LOCATION定位相关模块
QT += connectivityBR2_PACKAGE_QT5CONNECTIVITY蓝牙、NFC 等模块

注意几个点:

  • 模块名大小写敏感,.pro里写QT += SerialPort是不行的,必须是serialport;
  • 有些模块依赖其他模块,比如charts依赖widgets,multimedia依赖一堆底层库,buildroot 会自动处理依赖并勾选,不必手动逐个开;
  • webengine这个模块不建议在嵌入式板子上轻易开,依赖极多,编译极慢,最终镜像也大得吓人。

3.2 增量重建 Qt 模块,而不是全量刷机

配置勾选完以后,很多人会直接make,然后等半小时甚至几个小时。其实 buildroot 支持按包增量编译,效率高得多。

先单独编译目标模块:

make qt5serialport-rebuild

如果之前从来没编译过这个包,直接:

make qt5serialport

buildroot 会自动把这个包的依赖、源码下载、编译、安装到 staging 和 target 全流程走完。看到类似:

>>> qt5serialport 5.15.2 Installing to target directory

就说明模块已经安装进 target 目录了。

如果你不确定当前 buildroot 里这个包处于什么状态,也可以先 clean 再重建:

make qt5serialport-dirclean make qt5serialport

dirclean会把这个包的源码目录整个删掉,重新解压编译,适合包状态异常的情况。

3.3 检查 staging 和 target 的区别,这一步常常被人忽略

buildroot 里有两个目录的作用完全不同:

  • output/staging/:开发时编程序用的 sysroot,里面有头文件、静态库、.prl、.pc 等开发素材;
  • output/target/:最终根文件系统的内容,里面只有运行时要用的动态库和可执行文件。

交叉编译 Qt 程序时,链接器用的是 staging 里的库;程序拷到板子上运行时,加载的是 target 目录打包进镜像里的库。很多场景下,qmake 编译已经通过,但上板后一运行就报:

error while loading shared libraries: libQt5SerialPort.so.5: cannot open shared object file

原因就是 target 目录里根本没有这个库——也就是模块虽然编译进了 staging,但没进最终镜像。

所以 rebuild 完以后,务必确认:

ls -l output/target/usr/lib/libQt5SerialPort*

有输出才算真正装进了 rootfs。如果 staging 有、target 没有,常见原因是目标包被标记为“仅用于构建”或依赖关系没刷新,这时回到 buildroot 根目录做一次:

make

把依赖和 rootfs 打包流程整体走一遍。这一步会把 target 目录重新整理并生成最终的镜像文件,比如output/images/rootfs.ext4或者rootfs.tar。

3.4 重跑 qmake 和 make 时的两个坑

模块编译好了,回到你的工程目录,一定要把之前 qmake 生成的缓存清掉:

make clean rm -f Makefile output/host/bin/qmake make

为什么不直接make?因为 qmake 生成的 Makefile 里已经把“模块缺失”的结论固化进去了,即使 sysroot 里现在有词条了,不重新跑 qmake 它也不会重新解析。直接make可能还会继续报错,甚至报一些奇奇怪怪的链接错误。删掉 Makefile、重新 qmake 是最干净的。

第二个坑是:如果你的工程里原本就集成了宿主机 Qt 的环境,比如.pro里写了硬编码的/usr/lib/x86_64-linux-gnu/这种路径,那就算 buildroot 的 qmake 能识别模块,链接时也可能因为路径优先级问题选错库。所以交叉编译的工程,.pro里尽量只写相对路径和QT +=、CONFIG +=这种语义化配置,不要写死绝对路径。

4. 在全志 T113 这类板子上从编译到上板的完整闭环

4.1 解决“qmake 找不到”的最省事姿势

全志 T113 是 ARM Cortex-A7 双核,buildroot 工具链常见的前缀是arm-buildroot-linux-gnueabihf-。很多人拿到板子后直接敲qmake,系统说 command not found,就开始怀疑工具链没装好。其实不是工具链问题,是 qmake 不在 PATH 里。

最省事的做法是写一个环境脚本,把 buildroot 的 host 工具链加进 PATH。假设 buildroot 路径是/home/xxx/buildroot,脚本内容可以这样:

export BUILDROOT_DIR=/home/xxx/buildroot export PATH=$BUILDROOT_DIR/output/host/bin:$PATH export CROSS_COMPILE=arm-buildroot-linux-gnueabihf- export ARCH=arm

以后每次开终端先source env.sh,然后直接:

which qmake

就能看到/home/xxx/buildroot/output/host/bin/qmake。如果你的 buildroot 版本生成了output/host/environment-setup脚本,也可以直接 source 它,会把交叉编译器、qmake、pkg-config 这些路径一次性配置好。

4.2 完整交叉编译环境变量,照着抄就行

除了 PATH,还有几个环境变量在编译 Qt 程序时也经常绊人:

export PATH=$BUILDROOT_DIR/output/host/bin:$PATH export CROSS_COMPILE=arm-buildroot-linux-gnueabihf- export SYSROOT=$BUILDROOT_DIR/output/staging export PKG_CONFIG_PATH=$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig export PKG_CONFIG_SYSROOT_DIR=$SYSROOT

PKG_CONFIG_PATH和PKG_CONFIG_SYSROOT_DIR这两条尤其重要。Qt 模块在 buildroot 里都带.pc文件,如果 pkg-config 找不到它们,某些第三方库在探测 Qt 模块时也会失败。比如你后面要编一个依赖 QtSerialPort 的 CMake 工程,CMake 的find_package(Qt5SerialPort)本质上也是走这些.pc或者.cmake文件。

4.3 编译过了,上板运行又炸了?多半是库路径和插件路径问题

程序编好,拷贝到 T113 板子上,运行提示cannot open shared object file,这属于部署问题。先确认动态库依赖:

arm-buildroot-linux-gnueabihf-readelf -d ./your_app | grep NEEDED

找到libQt5SerialPort.so.5这类依赖后,在板子上确认/usr/lib下有没有对应文件。没有就把它从output/target/usr/lib/拷贝到板子的/usr/lib/,或者直接把新镜像烧进去。临时验证时可以设置:

export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH

但正式产品不要依赖这个变量,直接丢到/usr/lib或者用/etc/ld.so.conf配置才是正路。

更隐蔽的是 Qt 的插件路径问题。常见的现象是程序启动时报:

qt.qpa.plugin: Could not find the Qt platform plugin "linuxfb" in ""

这不是模块缺失,而是 Qt 的 QPA platform 插件(比如libqlinuxfb.so)没在预期路径。buildroot 编出来的插件在:

output/target/usr/lib/qt/plugins/

对应板子上的路径一般是/usr/lib/qt/plugins。如果启动时找不到,设置:

export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/lib/qt/plugins/platforms export QT_QPA_PLATFORM=linuxfb

再运行就能看到日志明显干净很多。嵌入式板子上,linuxfb是最常用的轻量 QPA 后端,适合没有 GPU 或不需要复杂合成场景的界面;如果你的板子有 GPU,比如 Mali 或者 PowerVR,buildroot 里配了 EGLFS 的话,可以用eglfs后端,显示效果更好。

4.4 从零到板端跑起来的最小链路

最后给一条我觉得最稳妥的验证链路。假设你的 buildroot 和工程都在手边:

  1. 在 buildroot 里make menuconfig勾上需要的 Qt 模块,比如qt5serialport;

  2. 退出后执行make qt5serialport-rebuild && make,确保模块装进 staging 和 target;

  3. 确认output/target/usr/lib/libQt5SerialPort*存在;

  4. 写一个最小工程,目录下放main.cpp和test.pro:

    #include <QCoreApplication> #include <QSerialPort> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QSerialPort port; qDebug() << "Qt SerialPort works"; return 0; }
    QT += core serialport CONFIG += console CONFIG -= app_bundle TARGET = test_serial TEMPLATE = app SOURCES += main.cpp
  5. source环境脚本后执行qmake && make,得到 ARM 版可执行文件test_serial;

  6. 拷贝到板子,export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/lib/qt/plugins/platforms,然后跑一下。

全程如果顺利,板子上会打印Qt SerialPort works。这里任意一步卡住,都能明确知道是 buildroot 打包问题、qmake 识别问题还是运行时路径问题。

我在实际项目中还遇到过一个比较隐蔽的情况:模块在 buildroot 里勾了,qt5serialport-rebuild也完成了,但.pc文件没进 staging,导致后来用 CMake 的工程死活 find 不到模块。这种情况直接把output/build/qt5serialport-5.15.2目录删掉重新make qt5serialport就能解决,比-rebuild更彻底。

还有一点个人体会:遇到这种 Unknown module(s) 报错,先别急着改 buildroot 配置。把qmake -query的输出截图存下来,对比一下模块词条是否存在,往往五分钟就能定位。真正花时间的不是编译,而是搞明白 qmake 这种“查词典”的工作方式。把这套机制想通了,以后换任何板子、任何 Qt 模块,排查思路都是一样的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询