在 Ubuntu 上装好 Qt Creator,新建或打开一个项目,点击左下角的绿色三角,下一秒就给你弹出一屏红色报错——这事我遇过太多次了。尤其是在刚装好系统的机器上,Qt Creator 项目编译失败几乎成了新环境里的“保留节目”,而且每次报错五花八门,有的明明代码没问题,却卡在环境上。这篇文章想把这类问题从“玄学”变成“有据可循”:我会按我实际排查的顺序,把 Ubuntu + Qt Creator 项目编译失败最常见的几个根源拆开讲,包括怎么读报错、怎么对工具链、怎么查依赖、怎么清缓存,最后给一套你随时可以拿来用的定位流程。不管你是刚入手 Qt 的新人,还是被编译问题烦了几天的老用户,应该都能在这里找到对应的解法。
1. 先分清错误出现在哪一层:别被第一屏红字带偏
很多人看到“编译失败”四个字就开始慌,其实 Qt Creator 的报错来源至少有三种,它们的排查方向完全不同。
1.1 问题面板、编译输出、应用输出,三者的分工要搞清楚
Qt Creator 底部默认有三个标签页:问题、搜索结果、编译输出,运行时还会多一个“应用输出”。我见过不少朋友只盯着“问题”面板看,结果里面的红字其实是代码静态检查或者 C++ 编译器的警告,根本不是构建失败的真正原因。最靠谱的做法是直接点开“编译输出”标签,这里才是编译器和链接器真正吐出来的原始日志。
举例来说,问题面板可能显示“No such file or directory”这种模糊描述,但编译输出里才会明确给出具体是哪一行、哪个 Makefile、哪条命令失败。链接阶段的错误更是如此,“undefined reference to XXX”通常只出现在编译输出的最后几行,问题面板未必能完整显示上下文。所以我的第一条经验就是:别只看问题面板,编译输出才是第一现场。
另一个容易混淆的是“应用输出”。如果你的程序能编译,但一运行就闪退,那属于运行时问题,要用调试器或看应用输出里的信号信息,和编译失败不是一回事。判断依据也很简单:构建进度条有没有走完、编译输出里有没有生成可执行文件的日志。这些信息在“编译输出”标签页顶部都有。
1.2 报错日志的“最后十行”价值最高
编译输出动辄几百行,经验不足的人容易从第一行开始读,越读越乱。我的习惯是直接滚动到末尾,从最后一个 error / undefined reference / cannot find 开始往回看。
因为编译过程是分层的:预处理器、编译器、链接器依次执行,前面的告警往往不影响流程,真正让构建中断的是最后一个退出码不为 0 的步骤。举个例子,如果一条命令是g++ -o myapp main.o ...并且报cannot find -lGL,说明前面的.o目标文件已经生成成功,问题出在链接库搜索路径上。这时候你回头去看头文件报错就是浪费时间,应该立即转向依赖库检查。
为了让日志更好读,我建议在 Qt Creator 里打开“工具 -> 选项 -> 构建与运行”,找到“构建输出”相关设置,勾选“滚动到末尾”或“在输出时滚动到末尾”。这样新日志不会把关键信息顶出屏幕,排查起来会顺畅很多。
2. 工具链与构建套件:Ubuntu 上编译失败的第一大来源
大多数 Ubuntu 全新安装后,并不自带完整的编译环境。Qt Creator 本身能打开,但一构建就提示各种命令找不到,这类问题根源几乎都在工具链。
2.1 最小安装环境下缺失的编译底座
Ubuntu 桌面版默认可能没有 gcc、g++、make,更别说 pkg-config。很多人装完 Qt Creator 后直接下载安装 Qt,界面很完整,但底层编译器不存在,于是一点构建就报“make: command not found”或者“Compiler cannot create executables”。
我建议在动手查项目代码之前,先跑一个命令确认底座是否完整:
which gcc g++ make cmake ninja pkg-config如果某些命令没有输出,就用 apt 安装。Ubuntu 20.04 / 22.04 上我常用这一套:
sudo apt update sudo apt install build-essential cmake ninja-build pkg-configbuild-essential会带上 gcc、g++、make 以及一些基础头文件,cmake对应 CMake 工程,ninja-build是很多新版 Qt 项目默认使用的生成器。装完之后回到 Qt Creator,在“工具 -> 选项 -> 构建与运行 -> Kits”里手动点击“重新检测”,让系统重新扫描一遍编译器。
有一个细节容易被忽略:如果机器的 Ubuntu 版本比较新,默认cmake版本可能很高,某些旧 Qt 项目不一定兼容。不过绝大多数情况下,用系统源里的合适版本就能正常构建,不必追求最新。
2.2 Kit 配置串线和 Qt 版本错位的经典症状
工具链没问题,不代表 Kit 没有问题。Qt Creator 里的“Kit”是把编译器、Qt 版本、调试器、CMake 绑定在一起的一套配置。我遇到过一个典型案例:某开发者的项目里明明写着QT += widgets,但编译输出却提示找不到qwidget.h。在编辑器里#include <QWidget>没有飘红,因为 Qt Creator 的“代码模型”用的是另一套解析器,它可能已经找到了某个 Qt 头文件路径,但真正编译时却走了另一个 Kit。
解决办法并不复杂:打开“工具 -> 选项 -> 构建与运行 -> Kits”,查看当前激活的 Kit,重点关注两个字段:
- 编译器:是否显示系统检测到的 gcc/g++,如果没有,就是编译器扫描失败。
- Qt 版本:是否显示类似 Qt 6.2 或 Qt 5.15 的路径,如果显示“无”或者路径不存在,构建时就没有 qmake 规则,自然无法生成 Makefile。
修复方式通常是:先确认/opt/Qt/版本号/gcc_64/bin/qmake或者系统 qt 包路径下的 qmake 真实存在,然后在 Kit 里重新指定。如果你是通过 apt 安装的 Qt5,qmake 通常位于/usr/bin/qmake;如果你手动安装 Qt 6,则多半在安装目录的gcc_64/bin/qmake下。
这里有个很容易误操作的点:有时候系统里同时存在 Qt5 和 Qt6,CMake 的find_package(Qt6)会找 Qt6,而 Qt Creator 里你选的 Kit 却绑定到了 Qt5,于是报错信息里一会儿“Found Qt5”,一会儿“Qt6 not found”,看着十分分裂。避免混淆的办法是进入 Kit 设置页逐一核对版本号,不要光看路径名字像就往下走。
2.3 sudo 装完的依赖,为什么项目里还是找不到
另一个高频场景:你明明用sudo apt install libqt5-dev之类的命令装了开发库,但项目编译还是提示缺少某些 Qt 模块的头文件。这里常见原因不是库没装,而是你手动设置的构建目录里缓存了旧的搜索路径,或者 Qt Creator 运行时的用户环境与终端环境不一致。
我特别提醒一点:不要用 sudo 启动 Qt Creator。一旦用它启动,项目构建目录里的文件可能生成成 root 所有,之后你再以普通用户打开项目,就会遇到权限拒绝,而且很难一眼看出。正确的做法是在普通用户下启动 Qt Creator,依赖库的安装则通过 sudo 完成,安装后重启 Qt Creator 让它重新扫描环境。
如果你的项目是 CMake 工程,改完系统依赖后,CMake 不一定立刻感知新的库路径。因为 CMake 会把探测结果写进CMakeCache.txt。表面上看是“apt 装了依赖还不行”,实际是 CMake 缓存里的CMAKE_PREFIX_PATH还停留在旧状态。下一个小节我会专门讲这两个工程的依赖查找机制,这也是编译失败里最容易踩的重灾区。
3. qmake 与 CMake 两种工程方式的依赖断点
Ubuntu 上既有用 .pro 文件的老工程,也有用 CMakeLists.txt 的新工程。它们的依赖查找逻辑不同,出错的形态也完全不同。
3.1 qmake 工程里QT += widgets是如何决定头文件路径的
qmake 会读取你的.pro文件,比如:
QT += core gui widgets TARGET = demo TEMPLATE = app SOURCES += main.cpp MainWindow.cpp HEADERS += MainWindow.h当你点击构建时,qmake 先从 Qt 安装信息里读取“模块配置”,比如 Qt5Widgets 的头文件在哪、库文件叫什么,然后把对应的-I和-l参数写进生成的 Makefile。如果系统里缺少匹配的开发包,qmake 不会直接报“缺少 Qt5Widgets”,而是生成的 Makefile 里相关变量为空,编译时表现为找不到头文件,链接时表现为找不到库。
Ubuntu 上使用系统 Qt5 时,最常见的补包命令是:
sudo apt install qtbase5-dev qtbase5-dev-tools装完你会发现/usr/include/x86_64-linux-gnu/qt5/QtWidgets这类目录出现了,qmake 才能正确解析QT += widgets。如果还用到其他模块,比如QT += network或QT += multimedia,就对应安装libqt5network5、libqt5multimedia5,以及对应的-dev包。一个更省力的做法是安装qt5-default元包(某些版本下可能叫qtbase5-dev),它会把常见模块都拉进来。
还有一类问题:Qt Creator 自动生成的 Makefile 与你在终端手动运行 qmake 的路径不一致。比如终端里输入qmake指向/usr/bin/qmake,而 Qt Creator Kit 里指向了/opt/Qt/.../qmake,两者解析出的模块路径就不同。尽量让项目的每个环节都用同一个 qmake。我处理这类问题时,会先在终端执行which qmake,再看 Qt Creator Kit 里的 Qt version 路径是否与其一致。
3.2 CMake 工程的find_package失败与 Qt6/Qt5 前缀路径
CMake 工程的报错经常长这样:
CMake Error at CMakeLists.txt:2 (find_package): By not providing "FindQt6.cmake" in CMAKE_MODULE_PATH, this project has asked CMake to find a package configuration file provided by "Qt6", but CMake did not find one.这句报错的本质是:CMake 不知道你的 Qt 安装在哪里。它默认会去一些标准目录搜,如果 Qt 被手动安装在/opt/Qt下,就必须告诉它前缀路径。
通常的 CMakeLists.txt 开头会长这样:
cmake_minimum_required(VERSION 3.16) project(demo VERSION 1.0) find_package(Qt6 REQUIRED COMPONENTS Widgets) qt6_add_executable(demo main.cpp MainWindow.cpp) target_link_libraries(demo PRIVATE Qt6::Widgets)如果你没有在 CMake 里写CMAKE_PREFIX_PATH,可以直接在 Qt Creator 的 Kit 设置里,找到“CMake Configuration”字段,加入一行:
CMAKE_PREFIX_PATH:STRING=/opt/Qt/6.5.0/gcc_64或者临时用命令行调试:
cmake -S . -B build -DCMAKE_PREFIX_PATH=/opt/Qt/6.5.0/gcc_64很多新手在这里卡住:明明安装了 Qt 6,但 CMake 报找不到。原因就是 CMake 并不会自动扫描/opt下所有目录,它只认已知路径。同理,如果你切换了 Qt 版本,比如从 Qt 5 升到 Qt 6,旧的CMakeCache.txt里残留的路径信息会导致 CMake 继续去找 Qt5,这时清理构建目录几乎是必做步骤。
我还有一个习惯:把qtbase5-dev和qt6-base-dev这两套包尽量分开装,避免同一台机器上环境和缓存互相干扰。如果必须共存,至少要在 Kit 设置里给不同项目绑定明确的 Qt 版本,不要混用。
4. 链接阶段是重灾区:undefined reference 与 cannot find -l 的两条追查链路
编译阶段能过去,不代表构建一定能成功。链接阶段出的错往往更隐蔽,因为它和系统库、动态库搜索路径、Qt 模块依赖都有关。
4.1 为什么“编译过了”却“链接一屏红”
编译和链接是两个阶段。编译负责把.cpp变成.o,链接负责把多个.o和库文件合成一个可执行文件。前者找不到头文件会立即报错,后者找不到库则是另一种报错形式。
一个非常典型的场景是你的代码里写了:
#include <QApplication>编译没问题,因为头文件路径存在。但链接时会报:
undefined reference to `QApplication::exec()'这说明头文件存在,但对应的库libQt5Widgets.so没有被链接进来。多数原因是.pro里忘了QT += widgets,或者 CMake 的target_link_libraries没有写Qt6::Widgets。Qt 的头文件只是声明,真正的函数实现都在共享库里,链接器不会替你自动推导。
另一类典型链接错误是找不到系统库,比如 OpenGL、X11、xcb。你会在编译输出底部看到:
/usr/bin/ld: cannot find -lGL collect2: error: ld returned 1 exit status这个-lGL对应的是libGL.so。Ubuntu 上需要安装 OpenGL 开发库才能找到它。
4.2 真实案例:一条 cannot find -lGL 的完整排查链路
我提供一个可以复现的排查过程。假设报错内容就是上面的cannot find -lGL,我不会着急改项目文件,而是按下面几步走:
第一步,确认系统里到底有没有libGL.so:
ldconfig -p | grep libGL如果输出里有类似libGL.so.1的条目,说明运行库存在,但开发用的符号链接libGL.so可能缺失。Ubuntu 上为了让链接器找到不带版本号的libGL.so,需要安装:
sudo apt install libgl1-mesa-dev第二步,再次构建。如果还报同一个错误,检查架构是否匹配。比如系统是 64 位,但你项目被某个 32 位工具链接管,链接器会去/usr/lib/i386-linux-gnu找库,自然找不到。打开 Kit 设置确认编译器是x86_64-linux-gnu-g++,而不是装了奇怪的交叉编译器。
第三步,检查 CMake/qmake 缓存。因为 CMake 在第一次配置时已经记下了“libGL 不存在”的结论,即使你现在装了libgl1-mesa-dev,它也不会自动刷新。你需要删除构建目录,或者打开 CMake Configuration 手动清掉缓存相关变量,然后重新构建。这个坑我踩过很多次:包装好了,库也存在,但 CMakeCache 还停留在旧状态,白白浪费半小时。
第四步,如果项目使用了 Qt 的 OpenGL 模块,确保也安装了libqt5opengl5-dev。有些 Qt 版本会引入Qt5::OpenGL组件,光装 mesa 还不够。
4.3 “vtable for … not found” 其实指向 Qt 元对象系统
还有一种很经典的链接错误:
undefined reference to `vtable for MainWindow'初次见会以为是自己代码漏写了函数,但真实原因往往是moc 没有正确运行。Qt 的信号槽机制依赖元对象编译器 moc,凡是类里写了Q_OBJECT,都需要在编译前调用 moc 生成moc_xxx.cpp。qmake 和 CMake 都会自动处理这一步,但如果.pro文件里没有把对应头文件加进 HEADERS,或者 CMake 里没有用qt6_wrap_cpp/ AUTOMOC,moc 就不会执行。
检查方法:看编译输出里有没有moc_MainWindow.cpp的编译步骤。如果完全没有,重点检查头文件是否被工程文件正确包含。另一个常见诱因是项目里有同名源文件和头文件,导致构建系统生成了错误的依赖,清理后重编往往能解决。
我在实际项目里还遇到过“两个类互相包含”导致的 vtable 问题,但那属于代码架构层面,需要调整头文件依赖,构建系统的修改救不了。所以遇到 vtable 报错,先分清楚是 moc 没跑,还是类的虚函数真没实现。
5. 三个“看似无关”的坑:路径、缓存与目录权限
这些坑不会天天遇到,但一旦碰上,往往比缺库更难排查,因为它们不会给出清晰直接的报错。
5.1 项目路径里的空格、中文和符号链接
Qt Creator 对项目路径比较敏感。如果路径里带空格,qmake 生成的 Makefile 在某些规则下会解析出错,报一些奇怪的No such file or directory;中文路径则可能因为系统 locale 设置而出现编码问题。
最稳妥的建议是:项目放在纯英文、无空格、无特殊符号的路径下,比如~/workspace/myapp。如果你的共享目录或移动硬盘上有现成项目,建议先拷贝到本机再打开。
还有一类容易被忽略的是符号链接。比如你项目实际存放在/data/myapp,但在~/workspace/myapp建了软链接指向它。Qt Creator 有时候会解析出不同路径,生成 Makefile 时产生相对路径混乱。遇到诡异报错,我会先试一遍将项目复制到无软链接的绝对路径,排除干扰。
5.2 旧构建目录里的 CMakeCache.txt 如何造成“假性”编译失败
CMake 的缓存文件是排查时需要重点盯住的对象。它的作用是把第一次配置时的结果持久化,下次构建时不再重新探测。这本来是为了省时间,但当你换了 Qt 版本、装了新的依赖库后,它就成了坏数据的温床。
我之前遇到一个项目:Qt 5 升级到 Qt 6 后,系统里两个版本都存在,CMake 依然沿着旧缓存的路径去找 Qt5,于是报错始终是“Qt5 not found”或者“Qt6 组件缺失”。解决办法就是删除构建目录里的CMakeCache.txt,最保险的是直接删除整个构建目录(比如build/),然后重新配置。
但要注意:直接删除构建目录会丢失所有编译中间文件,意味着全量重编。如果项目很大,可以先只删CMakeCache.txt和CMakeFiles目录,这也是一个折中方案。qmake 项目则看.qmake.stash文件,它记录了之前的配置状态,删除后重建即可。
5.3 Qt Creator 自身的进程残留与锁文件
最后一个干扰因素来自 Qt Creator 本身。比如上一次构建被强行中止,后台可能残留make或ninja进程,它们占用了目标文件或锁文件。下一次构建时,构建器尝试写入同一个.o文件,可能提示 Permission denied 或者 “text file busy”。
这种问题处理起来很简单:
ps aux | grep -E "make|ninja|qmake|g++"把残留进程结束掉,回到 Qt Creator 里执行“清理干净”或手动删除构建目录,然后重新构建。如果还不行,完全退出 Qt Creator,再重启一次。现实中很多“莫名其妙的编译失败”,其实只是上一次崩溃后的残留状态。
我个人的倾向是:每次大规模更换依赖之前,先清理构建目录;每次从别人那里接手项目,第一步不是编译,而是看构建目录是否带着上一台机器的缓存。这两条习惯能省下大量时间。
6. 一套通用排错流程:从报错到可运行的五步定位法
遇到编译失败,与其一个坑一个坑地试,不如按顺序完整走一遍定位流程。我用这套方法在这类问题上基本没有失手过。
6.1 五个步骤,把问题压缩到最小范围
第一步,验证工具链。在终端跑一下:
g++ --version cmake --version qmake --version确认这些命令都能执行,且版本和 Qt 模块兼容。Qt Creator 里再去 Kit 设置页做一次重新检测,这一步能排出工具链缺失。
第二步,验证最小工程。新建一个空项目,只保留一个 main.cpp 和一个空的 QMainWindow,不依赖任何第三方库。如果它能编译运行,说明 Qt 环境整体正常;如果不能,问题在 Qt 安装或 Kit 绑定的底层环境,不在项目代码。这个“最小可复现样本”方法,是我排查一切编译问题最核心的习惯。
第三步,查看编译输出最后的错误段落。如果是fatal error: xxx.h,属于头文件搜索路径或依赖包缺失;如果是undefined reference,属于库链接问题;如果是cannot find -lXXX,属于系统库或架构不匹配。把错误归类之后,再对照前面几个章节的方法处理,效率远高于盲目搜索。
第四步,清理并重建。无论改了什么设置,最后一步都要在干净状态下重新构建一次。因为我发现超过四分之一的“编译失败”,换个干净目录重新构建就自动好了。这听起来有点奇怪,但缓存问题和增量构建不一致就是能造成这种怪现象。
第五步,验证运行时依赖。即便构建成功,运行阶段可能还会报 “error while loading shared libraries: libQt5Widgets.so.5”。这是运行时找不到共享库,用 ldd 检查可执行文件:
ldd ./demo如果输出里有not found,就把对应 Qt 库目录加入LD_LIBRARY_PATH,或者在系统配置里加进/etc/ld.so.conf.d/并执行sudo ldconfig。这一步属于构建成功之后的最后一公里,虽然严格说不算“编译失败”,但我始终把“能运行”当作整个构建流程的完成标志。
6.2 把排错流程沉淀成个人检查单
最后分享一点个人经验。每次被这类问题折腾完,我都会在项目里放一个BUILD_NOTE.md文件,记录三件事:本项目的编译依赖有哪几个 apt 包、Qt Kit 绑定的是哪个版本、构建时最容易踩的坑是什么。下次换一台机器,或者同事接手,这几行笔记能直接省掉一个小时的试错时间。
比如有一个模拟项目 X,我在里面写了大意这样的记录:
依赖: build-essential, qtbase5-dev, libgl1-mesa-dev Kit: Qt 6.5.0, Desktop gcc 注意: 修改模块后清理整个 build 目录再编译这份检查单不需要写得多正式,但一定要围绕“当时是怎么定位到问题的”来写。因为编译失败的根因往往不可见,真正有价值的是定位路径。
如果你现在手上正好有一个 Ubuntu 项目编译失败,我的建议是:先不要急着在论坛里复制粘贴报错,打开编译输出,把最后十行原文读一遍,再按上面五步走。大多数情况下,二十分钟内就能定位。实在不行,再从最简单的“新建空项目能不能编译”开始,逐步缩小范围。这种逐步缩小、归类错误、清理重建的思路,比任何一个具体命令都更通用。