解决buildroot交叉编译Qt qmake Unknown module错误
先说一句这个问题我真的见了不知道多少次板子上用的 buildrootQt 环境是交叉编译的写了个 .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_QT5SERIALPORTQT charts对应BR2_PACKAGE_QT5CHARTSQT 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 能识别的记录文件。比如查 serialportfind 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 和 .priqmake 照样不认识。2.4 第四步查 buildroot 的 .config 确认模块开关直接看配置grep -i serialport .config如果输出有BR2_PACKAGE_QT5SERIALPORTy说明 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依赖widgetsmultimedia依赖一堆底层库buildroot 会自动处理依赖并勾选不必手动逐个开webengine这个模块不建议在嵌入式板子上轻易开依赖极多编译极慢最终镜像也大得吓人。3.2 增量重建 Qt 模块而不是全量刷机配置勾选完以后很多人会直接make然后等半小时甚至几个小时。其实 buildroot 支持按包增量编译效率高得多。先单独编译目标模块make qt5serialport-rebuild如果之前从来没编译过这个包直接make qt5serialportbuildroot 会自动把这个包的依赖、源码下载、编译、安装到 staging 和 target 全流程走完。看到类似 qt5serialport 5.15.2 Installing to target directory就说明模块已经安装进 target 目录了。如果你不确定当前 buildroot 里这个包处于什么状态也可以先 clean 再重建make qt5serialport-dirclean make qt5serialportdirclean会把这个包的源码目录整个删掉重新解压编译适合包状态异常的情况。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_COMPILEarm-buildroot-linux-gnueabihf- export ARCHarm以后每次开终端先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_COMPILEarm-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$SYSROOTPKG_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_PLATFORMlinuxfb再运行就能看到日志明显干净很多。嵌入式板子上linuxfb是最常用的轻量 QPA 后端适合没有 GPU 或不需要复杂合成场景的界面如果你的板子有 GPU比如 Mali 或者 PowerVRbuildroot 里配了 EGLFS 的话可以用eglfs后端显示效果更好。4.4 从零到板端跑起来的最小链路最后给一条我觉得最稳妥的验证链路。假设你的 buildroot 和工程都在手边在 buildroot 里make menuconfig勾上需要的 Qt 模块比如qt5serialport退出后执行make qt5serialport-rebuild make确保模块装进 staging 和 target确认output/target/usr/lib/libQt5SerialPort*存在写一个最小工程目录下放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.cppsource环境脚本后执行qmake make得到 ARM 版可执行文件test_serial拷贝到板子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 模块排查思路都是一样的。