Qt项目用Ninja构建时修改UI文件后cpp.obj未重新生成问题
我用Ninja+MSVC2019+CMake构建Qt项目,现在碰到个问题:修改UI文件后重新构建,ui_mainwindow.h会重新生成,但mainwindow.cpp.obj不会跟着编译,导致界面修改不生效。之前用NMake+MSVC2019没这问题,但NMake太慢才换的Ninja,现在得找出Ninja不重新生成obj的原因。
相关代码文件
CMakeLists.txt
cmake_minimum_required(VERSION 3.5) project(NinjaTest VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) set(PROJECT_SOURCES main.cpp mainwindow.cpp mainwindow.h mainwindow.ui ) qt_add_executable(NinjaTest MANUAL_FINALIZATION ${PROJECT_SOURCES} ) target_link_libraries(NinjaTest PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) set_target_properties(NinjaTest PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} MACOSX_BUNDLE TRUE WIN32_EXECUTABLE TRUE ) install(TARGETS NinjaTest BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(NinjaTest) endif()
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "./ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); } MainWindow::~MainWindow() { delete ui; }
main.cpp
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>600</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralwidget"> <widget class="QLabel" name="label"> <property name="geometry"> <rect> <x>260</x> <y>260</y> <width>63</width> <height>20</height> </rect> </property> <property name="text"> <string>COUCOU</string> </property> </widget> <widget class="QPushButton" name="pushButton"> <property name="geometry"> <rect> <x>280</x> <y>80</y> <width>83</width> <height>29</height> </rect> </property> <property name="text"> <string>PushButton</string> </property> </widget> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>25</height> </rect> </property> </widget> <widget class="QStatusBar" name="statusbar"/> </widget> <resources/> <connections/> </ui>
核心原因
CMake的AUTOUIC机制默认会把ui_mainwindow.h生成在构建目录的自动生成文件夹(比如build/CMakeFiles/NinjaTest.dir/autogen/include/),但你的mainwindow.cpp里写的是#include "./ui_mainwindow.h"——这个相对路径会让编译器优先去源码目录找文件,而非构建目录里的最新生成版。
Ninja的依赖检查比NMake严格得多:它只会追踪代码里显式写的依赖路径,发现源码目录里的ui_mainwindow.h没变化,就不会触发mainwindow.cpp.obj的重新编译。而NMake依赖检查的松散性恰好绕过了这个问题。
解决步骤
修改头文件包含路径
把mainwindow.cpp里的#include "./ui_mainwindow.h"改成#include "ui_mainwindow.h",去掉./前缀。这样编译器会按照CMake配置的包含路径,优先找到构建目录里的自动生成文件,Ninja也能正确追踪到ui_mainwindow.h和mainwindow.cpp的依赖关系。清理构建目录重新生成
删除整个构建目录,重新运行CMake配置命令(比如cmake -G Ninja -S . -B build),再用ninja -C build构建。之后修改UI文件,就能触发mainwindow.cpp的重新编译,界面修改会正常生效。可选:自定义AUTOUIC生成目录(进阶)
如果需要指定生成文件的位置,可在CMakeLists.txt中添加:set(CMAKE_AUTOUIC_OUTPUT_DIR ${CMAKE_BINARY_DIR}/ui) target_include_directories(NinjaTest PRIVATE ${CMAKE_BINARY_DIR}/ui)不过默认配置下,只要去掉
./前缀就足够解决问题,无需额外配置。
内容的提问来源于stack exchange,提问作者Fdo7

