尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Buildroot下qmake编译Qt报Unknown module的排查与解决

Buildroot下qmake编译Qt报Unknown module的排查与解决 写这篇博文源自一个非常经典的嵌入式开发痛点在buildroot生成的工具链里用qmake编译自己的Qt程序结果终端刷出一行“Project ERROR: Unknown module(s) in QT: xxxx”然后编译中止。刚接触这个组合的人很容易懵因为单独跑buildroot、单独跑qmake都没问题但两者一结合就报错。我把整个排查过程和最终解决方案整理成文覆盖从环境变量、qmake路径到buildroot的Qt模块裁剪、mkspec配置再到交叉编译时库路径的完整链路。这篇文章不是简单的报错复制粘贴而是把背后“为什么找不到模块”的逻辑讲清楚让遇到类似问题的人能举一反三彻底解决这一类的编译期异常。1. 问题现象与核心原因定位1.1 先看报错的真实含义“Unknown module(s) in QT: serialport”这句报错直译是“在QT配置项中发现了未知模块serialport”但它真正传递的信息是qmake在查找模块时既没有找到对应的头文件搜索路径也没有找到对应的库链接路径。更直白地说qmake在解析你的.pro文件时遇到了QT serialport这行配置然后它去自己记录的模块清单里找serialport结果发现这个模块“查无此人”于是直接抛错退出。这里有个关键点qmake本身并不是在编译阶段才去检测模块而是在生成Makefile的阶段就会检测。也就是说Unknown module是一个发生在生成构建系统阶段qmake步骤的配置错误而不是发生在make阶段的编译错误。如果你用Qt Creator打开工程报错通常出现在“构建”日志里但实际上是qmake命令执行失败根本没有走到编译那一步。这个错误有一个很迷惑人的地方你明明在buildroot的menuconfig里已经勾选了Qt SerialPort模块而且buildroot也成功编译出了libQt5SerialPort.so为什么qmake还是说不认识这个模块问题的根源不在库文件而在qmake的模块信息文件——也就是那些.pri文件和mkspec配置——没有被正确加载。1.2 为什么会突然出现“Unknown module”我把常见的原因梳理一下基本逃不出下面这几类qmake路径不对你调用的qmake是宿主机上Qt Creator自带的qmake而不是buildroot在output/host目录下生成的交叉编译qmake。宿主机qmake的模块清单里当然没有你为ARM板子编译的serialport模块。QT模块没有真正编译buildroot的Qt配置界面里某些模块并不是默认勾选的。比如QtSerialPort、QtCharts、QtDataVisualization这类非核心模块往往藏在子菜单里你可能勾选了Qt库本身但忘了勾选具体的功能模块。mkspec配置问题buildroot生成的qmake默认查找模块信息的路径是通过编译时的QT_INSTALL_PREFIX和QT_INSTALL_ARCHDATA等路径决定的。如果你在buildroot配置里改了BR2_PACKAGE_QT5BASE相关的安装路径或者用了外部工具链可能导致qmake去错误目录查找模块描述文件。sysroot环境变量缺失交叉编译时qmake需要知道目标板的根文件系统在哪里才能找到usr/lib下的Qt库文件和usr/lib/qt5/mkspecs下的模块配置。如果没有通过-sysroot参数或QT_SYSROOT环境变量指定qmake就会去宿主机的/usr/lib找那当然什么都找不到。我自己第一次踩这个坑是在全志T113平台上做板卡适配。buildroot版本是2022.02Qt版本是5.15交叉编译工具链用的是buildroot内部自带的gcc 10.3。整套系统能编译出带Qt的固件板子跑起来后Qt程序也能跑但只要一在自己的工程里加QT serialport就立刻报Unknown module(s) in QT: serialport。折腾了大半天最后发现就是qmake路径的问题——我在Qt Creator里配的是宿主机的Qt 5.15.2msvc2019_64那套而不是buildroot生成的arm-linux-gnueabihf-qmake。1.3 判断你是哪一种原因在动手解决之前先用几个命令快速定位问题类型。最重要的一步是确认你当前用的是哪个qmake。which qmake如果在宿主机上执行后显示的是/usr/bin/qmake或者Qt Creator安装目录下的某个qmake那几乎可以确定就是第一个原因qmake路径不对。真正的buildroot qmake藏在你的output目录下通常形如output/host/bin/qmake如果你的buildroot配置了外部工具链名字可能带前缀比如arm-linux-gnueabi-qmake或aarch64-linux-gnu-qmake。查看一下这个qmake的属性可以验证它是不是真正的交叉编译版本output/host/bin/qmake -query正常输出的内容里QT_SYSROOT一栏应该指向你的buildroot输出目录下对应的staging目录QT_INSTALL_PREFIX应该指向目标板的/usr路径QT_INSTALL_ARCHDATA、QT_INSTALL_LIBS等也应该与目标板布局一致。如果这些路径指向了宿主机目录说明qmake环境没有配置对。2. buildroot环境下的qmake正确配置方式2.1 先搞清楚buildroot的Qt模块裁剪逻辑buildroot的一个核心设计理念是“要什么编什么”。它在menuconfig里提供了极其细粒度的配置选项包括Qt库的每个模块是否编译。这意味着你不仅需要勾选Qt5base这个基础库还需要在它的子菜单里勾选具体的功能模块。以最常见的SerialPort模块为例配置路径是Target packages - Graphic libraries and applications - Qt5 - Qt5base - [*] Qt SerialPort module注意这个选项不是默认选中的。如果你用默认配置或者只勾选了Qt5base那么buildroot只会编译出Qt基础模块Core、Gui、Widgets、Network这些SerialPort相关的源码根本不会进入编译流程自然也不会生成libQt5SerialPort.so更不会生成对应的qt5serialport.pri模块描述文件。判断一个模块是否被编译可以在buildroot的output目录里查看find output/build/qt5serialport-* -name *.so 2/dev/null或者更直接find output/target/usr/lib -name *SerialPort*如果结果为空说明模块没有编译或者编译了但没有被安装到target目录。这里需要特别提醒一点修改buildroot配置后必须重新编译Qt模块本身而不是只make你的工程。很多人改了menuconfig之后直接回到自己的工程目录运行qmake发现还是报一样的错原因就在这里——buildroot的增量编译机制下它不一定会在下次编译时自动重建之前已经构建过的Qt模块。你必须手动清理相关模块的编译产物强制它重新编译make qt5serialport-dirclean make qt5base-rebuild makedirclean是buildroot里很实用的一个清理指令它会删除指定软件包的build目录和stamp文件相当于把这个包打回“未编译”状态。这样下次make时buildroot就会重新拉取源码、重新配置、重新编译这个模块。2.2 mkspec的正确选择至关重要buildroot实际上为每个目标平台都预配置好了对应的Qt mkspec位于output/host/lib/qt5/mkspecs目录下。当你使用buildroot生成的qmake时它会自动根据编译时的目标平台选择合适的mkspec这就是为什么你不需要手动指定-platform linux-arm-gnueabi-g之类参数。但问题在于如果你在命令行里自己创建了一个Qt Creator kit或者在一个Makefile里手动指定了QMAKE_PLATFORM就很容易覆盖掉buildroot的默认配置导致qmake用宿主机的mkspec去生成Makefile。这个生成的Makefile里编译器可能是g而不是交叉编译工具链库路径可能是/usr/lib而不是staging目录结果自然是一片混乱。最稳妥的做法是直接用buildroot生成的qmake不要手动指定平台参数。也就是说应该这样调用source output/host/environment-setup qmake你的工程.proenvironment-setup文件里配置了CC、CXX、CFLAGS、LDFLAGS等全套环境变量source之后qmake生成的Makefile会自动带上正确的交叉编译参数。这比手动在命令行里传一堆-spec参数可靠得多也免得自己记那些复杂的配置项。如果你用的buildroot版本比较新可能在output/host下没有environment-setup这时可以用一条命令手动初始化环境export PATH$PWD/output/host/bin:$PATH export STAGING_DIR$PWD/output/host export PKG_CONFIG_PATH$PWD/output/host/lib/pkgconfig然后在同一个终端会话里运行qmake。注意这四个环境变量必须同时设置缺少任何一个都可能导致路径查找异常。PATH负责找到qmake和编译器STAGING_DIR负责告诉编译器和链接器目标板的系统根在哪PKG_CONFIG_PATH负责让pkg-config找到Qt模块的.pc文件。2.3 用qmake -query检查模块路径是否正常配置好环境后用qmake -query命令检查一下路径是否正确。重点看这几个输出项。qmake -query输出里需要重点关注的项配置项正常值示例QT_SYSROOT你的buildroot路径/output/host/... 或staging目录QT_INSTALL_PREFIX/usr (目标板上的安装前缀)QT_INSTALL_LIBS/usr/libQT_INSTALL_ARCHDATA/usr/lib/qt5QT_INSTALL_ARCHDATA/mkspecs 路径/usr/lib/qt5/mkspecs有一个很隐蔽的坑有时候QT_INSTALL_PREFIX看起来是/usr而QT_SYSROOT指向的是buildroot的输出目录但qmake实际查找模块时用的路径是QT_SYSROOT QT_INSTALL_PREFIX /lib这样的组合。如果QT_SYSROOT没有正确设置就会出现qmake去/usr/lib宿主机而不是output/host/usr/lib目标板查找模块的情况。遇到这种问题可以直接指定sysroot来覆盖qmake -sysroot /path/to/your/buildroot/output/host your_project.pro或者修改你的.pro文件在文件顶部显式声明QMAKE_SYSROOT /path/to/your/buildroot/output/host当然这种硬编码方式不利于工程迁移不推荐长期使用。我更建议在构建脚本里通过环境变量动态传入比如qmake -sysroot $STAGING_DIR your_project.pro3. 通用解决方案从检测到修复的完整流程3.1 第一步确认qmake指向执行以下命令确认当前使用qmake是buildroot生成的交叉编译版本。which qmake qmake -v如果输出中带有arm-linux-gnueabi-g或类似前缀例如QMake version 3.1 Using Qt version 5.15.2 in /home/user/buildroot/output/host/lib/qt5那么qmake路径正确。如果输出是/usr/bin/qmake或Windows上的D:\Qt\5.15.2\msvc2019_64\bin\qmake.exe说明你用错版本了——这也是热词搜索里“全志t113 qmake找不到”这类问题最常见的原因。3.2 第二步确认模块在buildroot中已启用回到buildroot目录运行make menuconfig进入Qt配置路径确认对应的模块已经被勾选。只是一个勾选操作但需要知道具体位置下面列几个常用模块的路径功能模块menuconfig 路径SerialPortTarget packages - Graphic libraries and applications - Qt5 - Qt5base - SerialPort moduleChartsTarget packages - Graphic libraries and applications - Qt5 - Qt5chartsNetwork默认包含Target packages - Graphic libraries and applications - Qt5 - Qt5base - Network moduleSqlTarget packages - Graphic libraries and applications - Qt5 - Qt5base - Sql module注意在buildroot较新的版本中菜单层级可能略有差异但大体是Target packages - Graphic libraries and applications - Qt5 - ...这条路径。勾选后保存然后强制重建相关模块。如果模块没有编译或者你不确定buiroot是否已经包含了这个模块的编译结果可以直接执行make qt5base-rebuild但更稳妥的方式是先清理再编译make qt5base-dirclean make如果目标模块是一个独立的Qt包比如qt5charts注意不是qt5base的子模块则对应的清理指令是make qt5charts-dirclean。注意清理包的名称必须和menuconfig里的包名一致。3.3 第三步确认qmake能找到模块的.prf/.pri文件Qt的模块注册机制是通过mkspecs/modules目录下的.pri文件实现的。每个模块在构建时会生成一个qt_lib_xxx.pri文件这个文件里定义了include路径、lib路径和链接参数。qmake启动时会扫描QT_INSTALL_ARCHDATA/mkspecs/modules目录下的所有.pri文件建立“模块名 - 路径配置”的映射。如果这个.pri文件缺失或者路径配置错误qmake就会报Unknown module。检查这个目录ls /home/user/buildroot/output/host/lib/qt5/mkspecs/modules/这里应该能看到类似qt_lib_serialport.pri、qt_lib_charts.pri等文件。如果缺失说明模块没有正确进入qmake的模块注册表需要重新编译Qt模块。查看.pri文件的内容确认路径正确cat /home/user/buildroot/output/host/lib/qt5/mkspecs/modules/qt_lib_serialport.pri里面的内容应该是这样的结构QT_SERIALPORT_INCDIR /usr/include/qt5/QtSerialPort QT_SERIALPORT_LIBDIR /usr/lib QT_SERIALPORT_LIBS -lQt5SerialPort QT_SERIALPORT_VERSION 5.15.2这里的/usr/include/qt5/QtSerialPort和/usr/lib都是目标板上的路径不是宿主机路径。qmake会在后面自动拼接QT_SYSROOT前缀来定位实际文件。3.4 第四步使用正确的构建命令构建你的工程完成以上检查和修正后正确的构建命令流程应该是cd /path/to/your/project qmake your_project.pro make clean make -j$(nproc)这里有一个细节值得强调每次更换qmake版本后最好先make clean再重新qmake和make。因为旧的Makefile里记录的路径、编译器参数可能是旧配置直接make的话可能继续沿用旧参数导致报一些奇怪的链接错误让你误以为问题没有解决。3.5 第五步给Qt Creator配置正确的交叉编译套件如果你习惯在Qt Creator里开发而不是纯命令行那么问题通常出在Kit配置上。Qt Creator的Kit需要手动指定三样东西编译器Compilerbuildroot生成的gcc和g路径在output/host/bin/arm-linux-gnueabihf-gcc调试器Debuggeroutput/host/bin/arm-linux-gnueabihf-gdbQt版本Qt Versionoutput/host/bin/qmake配置路径Tools - Options - Kits先添加Qt Version再添加Compiler最后在Kits页面组合起来。配置完成后选择这个Kit重新构建工程qmake就能正确识别buildroot里的所有模块。补充一个实际经验在Qt Creator里添加qmake时如果弹出提示“No valid Qt version”通常是因为没有指定qmake的路径或者该qmake不是Qt 5.15的版本。确认qmake -v输出显示的是Qt 5.x版本而不是Qt 4.x否则在Kit里无法识别。4. 更隐蔽的坑库文件存在但模块注册表缺失4.1 模块编译了也被安装了但qmake还是不认识有一种更隐蔽的情况libQt5SerialPort.so存在/usr/lib下也有buildroot的target目录也有但qmake就是报错。这个问题出在Qt 5.15的模块注册机制上。Qt 5在构建模块时除了生成动态库还会在mkspecs/modules/目录下生成qt_lib_serialport.pri文件。这个文件的作用就是向qmake声明“存在一个叫作serialport的模块它的头文件在哪个目录、库文件在哪个目录”。如果这个.pri文件没有生成或者生成到了错误的位置qmake就不会把这个模块加入它的模块清单。检查这个文件是否存在于buildroot输出目录的qmake配套路径下find output/host -name qt_lib_serialport.pri正常情况下应该有结果。如果没有说明Qt SerialPort模块虽然编译了库文件但模块注册文件的生成环节出了问题需要重新编译该模块。强制rebuildmake qt5serialport-rebuild make这个过程中打印的日志会明确显示qt_lib_serialport.pri是否被复制到了正确目录。4.2 头文件路径需要显式声明另一种情况是模块注册表正常但实际编译时头文件找不到。这多见于你自己在模块注册表里注册了模块但系统的头文件搜索路径没有更新。检查你的Qt模块头文件路径例如ls /path/to/buildroot/output/staging/usr/include/qt5/QtSerialPort如果存在QSerialPort、QSerialPortInfo等头文件但qmake依然找不到可以尝试在你的.pro文件中显式添加头文件搜索路径INCLUDEPATH /path/to/buildroot/output/staging/usr/include/qt5/QtSerialPort LIBS -L/path/to/buildroot/output/staging/usr/lib -lQt5SerialPort这是一个比较“暴力”的解决方案但能快速验证问题到底出在哪个环节。如果这样改完能编译通过说明模块注册表的路径整错了如果还不行说明库文件本身没有安装到正确的sysroot里。4.3 检查Qt 模块的依赖关系某些Qt模块不是独立的它依赖其他模块。比如QtCharts依赖QtWidgets、QtDeclarativeQML等模块。如果你的buildroot配置里勾了QtCharts但忘了勾QtWidgets那么即使Charts模块编译成功在构建时也会出现类似Unknown module(s) in QT: widgets的报错或者更诡异的头文件冲突。解决方案是在menuconfig里把模块依赖项也一并勾选。这里建议查看buildroot源码目录下对应模块的Config.in文件里面明确记录了依赖关系。例如cat output/build/qt5charts-*/Config.in就能看到config BR2_PACKAGE_QT5CHARTS bool Qt Charts select BR2_PACKAGE_QT5BASE select BR2_PACKAGE_QT5DECLARATIVE ...根据这个清单把依赖项都在menuconfig中勾上。5. 常见问题与排查技巧实录5.1 报错常见的几种变体与对应处理我在实际使用中总结了下面几种最常见的报错变体以及对应的处理优先级。下面这张速查表可以直接拿去参考。报错内容核心原因处理优先级Unknown module(s) in QT: serialport模块未编译 / qmake路径错误高先检查qmake与模块编译状态Unknown module(s) in QT: chartsQt Charts模块未勾选或依赖缺失高menuconfig勾选相关模块Project ERROR: Unknown module(s) in QT: quickQt Quick/QML相关模块缺失中确认qt5declarative包已勾选Project ERROR: Unknown module(s) in QT: scriptQt Script模块未编译中确认qt5script包状态qmake: could not find a Qt installation of qmake严重配置错误高重新source环境变量或重建qmakeCannot read /usr/lib/qt5/mkspecs/qconfig.primkspec路径错误或者sysroot配置错误高检查qmake -query输出5.2 排查步骤口诀先看qmake后看模块再看注册表我还总结了一个三字排查法看、查、验。看看qmake指向哪里which qmakeqmake -v确认是buildroot的交叉编译版本。查查模块是否被编译去output/target/usr/lib下搜库文件查模块是否注册去output/host/lib/qt5/mkspecs/modules/搜.pri文件。验用一段最简单的测试代码验证问题是否解决。一个只包含QSerialPort的main.cpp一个只包含QT serialport的.pro文件。如果最小用例能编译过说明是你的工程配置问题如果最小用例也报错说明环境本身没配置好。很多人在排查时喜欢直接在庞大的工程上反复尝试浪费时间且难以定位。用一个最小的验证工程来测试环境是我个人极其推荐的做法能快速隔离出是环境问题还是工程配置问题。5.3 交叉编译时的环境变量陷阱buildroot在编译完整个系统后会在output/host目录下生成一套可以在宿主机上运行的交叉编译工具链。但是直接调用output/host/bin/arm-linux-gcc时系统可能提示找不到某些动态库。这是因为buildroot的host目录下的工具链为了不污染宿主机环境默认把库路径做成了绝对路径直接运行可能因找不到宿主机的某个.so文件而失败。在编译Qt工程时如果这个错误出现需要在调用qmake之前设置环境变量export LD_LIBRARY_PATH/path/to/buildroot/output/host/lib:$LD_LIBRARY_PATH但这里引出一个更深层的问题LD_LIBRARY_PATH设置不当可能影响pkg-config的查找结果进而导致qmake找到宿主机的Qt模块信息。更稳妥的做法是通过buildroot官方的environment-setup脚本cd /path/to/buildroot source output/host/environment-setup这个脚本不仅设置了PATH和LD_LIBRARY_PATH还正确配置了交叉编译相关的CFLAGS、CXXFLAGS、LDFLAGS。如果用的buildroot版本较老没有这个脚本可以手动配置上述环境变量并确保QT_HOST_BINS这种路径不要参与导入。5.4 全志T113平台的一个实际案例热词搜索里频繁出现“全志t113 qmake找不到”我分享一下自己在T113平台上的排查记录。T113是armv7架构的Cortex-A7双核处理器buildroot配置时选了cortex-a7的编译器优化参数。我在Qt Creator里新建了一个kit指向output/host/bin/arm-buildroot-linux-gnueabihf-gcc和output/host/bin/qmake。配置完成后看起来一切都对但编译时死活报Unknown module(s) in QT: serialport。最后排查发现问题出在Qt Creator的“Qt Versions”配置里有一个不起眼的选项“Source code”它默认填了一个无效路径。Qt Creator在检测qmake时会尝试解析qmake的可执行文件路径来推断Qt的安装目录。如果推断失败它就用一个默认前缀去拼接模块路径结果自然找不到。解决方案其实很简单在Tools - Options - Kits - Qt Versions里手动把qmake的可执行文件路径设置到output/host/bin/qmake系统会自动重新加载模块信息并正确识别所有Qt模块。如果仍然识别失败就在“Qmake Configuration”里手动添加一条QMAKE_SYSROOT/path/to/buildroot/output/host添加完后点击Apply重新构建即可。5.5 buildroot使用外部工具链时的额外注意事项如果你在buildroot里选择了使用外部工具链比如用Linaro的gcc而不是buildroot自己编译的内置工具链情况会更复杂。外部工具链的sysroot路径、库文件版本可能与buildroot的预期不一致导致qmake生成的Makefile里出现错误的库路径。遇到这种情况优先建议在buildroot的Toolchain配置里把“Copy gdb server to target”和“Build cross gdb for the host”这些选项正确设置并确保你的外部工具链的sysroot路径与buildroot的output/staging目录一致。具体来说在menuconfig里设置Toolchain - Sysroot path /path/to/your/external-toolchain/arm-linux-gnueabihf/libc如果配置不正确即使qmake能生成Makefile编译时也会出现找不到libQt5SerialPort.so这样的链接错误而不是报Unknown module。6. 从“能编译”到“能部署”的完整闭环6.1 静态链接还是动态链接部署时的模块问题编译通过不等于问题彻底解决。还经常遇到一种情况程序在buildroot环境里编译成功后拷贝到目标板上一运行提示无法加载libQt5SerialPort.so.5。这就涉及到Qt模块的动态链接依赖。如果你希望程序在目标板上运行时不需要依赖额外的动态库最简单的方法是在编译时静态链接Qt库。在buildroot的menuconfig里Target packages - Graphic libraries and applications - Qt5下有一个选项叫Qt5 static link不同版本名称略有差异勾选后编译出来的Qt库是静态库。但请注意静态链接Qt库会导致编译时间显著增加且生成的二进制文件很大对于存储空间有限的嵌入式设备需要考虑权衡。更常用的是动态链接方式。此时需要在buildroot配置里确保目标板根文件系统包含所有Qt模块的动态库。进menuconfig勾选对应模块后buildroot会在make阶段自动把这些模块安装到output/target/usr/lib目录。但如果你是在已经有根文件系统镜像的基础上只更新Qt库可能需要手动把编译好的.so文件拷贝到目标板。6.2 用ldd验证部署依赖最有效的验证手段是交叉编译环境中自带的arm-linux-readelf工具配合ldd命令检查你的可执行文件的动态库依赖output/host/bin/arm-linux-gnueabihf-readelf -d your_app输出里会列出程序运行时需要加载的所有动态库。核对NEEDED项确认其中包含libQt5SerialPort.so.5且没有引用宿主机上的库路径。6.3 多模块工程ANDROID式的模块声明技巧最后分享一个工程管理上的实用技巧。如果你的工程很大包含多个模块每个模块依赖不同的Qt库建议在.pro文件里采用分层声明的方式。比如主工程里声明基础模块子模块工程里声明各自依赖的模块# submodule_common.pri QT core gui widgets # submodule_serial.pri QT serialport # submodule_charts.pri QT charts然后在主工程里:include(submodule_common.pri) include(submodule_serial.pri) include(submodule_charts.pri)这种方式的优点是每一个子模块在编译时只会触发它自身依赖模块的检查不会因为主工程缺少某个模块信息而连带着其他模块也报错。对于维护大型Qt工程特别有帮助能快速定位是哪个子模块依赖了未编译的Qt库。根据我个人在buildroot Qt项目上的经验大多数Unknown module(s) in QT问题都不是Qt本身的bug而是环境串台了。要么用了错误平台的qmake要么是buildroot的模块没勾选要么是symroot路径没有正确传给qmake。按照本文的排查顺序先确认qmake身份再确认模块编译最后验证模块注册表绝大多数情况下能在十分钟内定位问题。如果这三步都没解决再去怀疑buildroot的缓存问题做一次make clean后重新编译整个buildroot环境。
返回列表