一、前言
為什么要用 CMake 來構建 Qt 的項目呢?Qt 不是有 qmake 嗎?這樣,豈不是多此一舉?
其實,應用 CMake 來構建項目還是非常有必要的,特別是當你的項目涉及到很多第三方庫的時候,CMake 的優勢非常突出。
Qt5.15.2 在之前選擇安裝模塊的時候,自動幫我們勾選了 CMake_64 模塊,你也可以另外選擇勾選 CMake_32 模塊,所以 QtCreator 是支持 CMake 編譯方式的,而不僅僅只能使用 QMake 編譯。
本人之前也手動安裝了 CMake3.20.0 的版本,這些可以在 QtCreator 中的套件配置的 cmake 項看到:
下面我們介紹一下 Qt 使用 CMake 編譯的兩種方式。
二、依賴QtCreator自動生成CMakeLists.txt文件
QtCreator 新建工程時,選擇 cmake 而不是默認的 qmake 編譯方式,如下所示:
然后選擇 CMake 要編譯成的哪種編譯套件,是 MingW 還是 MSVC,這里選擇的是 Qt5.15.2 MinGW 64,創建運行成功后:
可以看到 QtCreator 自動幫忙生成了 CMakeLists.txt 文件,直接可以使用,其內容如下:
cmake_minimum_required(VERSION 3.5)
project(HelloWorld LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# QtCreator supports the following variables for Android, which are identical to qmake Android variables.
# Check https://doc.qt.io/qt/deployment-android.html for more information.
# They need to be set before the find_package( ...) calls below.
#if(ANDROID)
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
# if (ANDROID_ABI STREQUAL "armeabi-v7a")
# set(ANDROID_EXTRA_LIBS
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so
# ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so)
# endif()
#endif()
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED)
set(PROJECT_SOURCES
main.cpp
widget.cpp
widget.h
widget.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(HelloWorld
${PROJECT_SOURCES}
)
else()
if(ANDROID)
add_library(HelloWorld SHARED
${PROJECT_SOURCES}
)
else()
add_executable(HelloWorld
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(HelloWorld PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
QtCreator 也幫忙添加了 CMake Modules,來支持對 Qt5Core、Qt5Gui 等模塊的支持。
就是后續在工程中添加其它的類,比如 Form 類,不會自動添加到 CMakeLists.txt 文件中,需要手動添加:
set(PROJECT_SOURCES
main.cpp
widget.cpp
widget.h
widget.ui
#下面為手動添加內容
form.cpp
form.h
form.ui
)
需要添加額外的模塊,同理。
當然自動生成的 CMakeLists.txt 有些累贅的內容,比如對 ANDROID 的配置,下面會介紹一下如何自己編寫 CMakeLists.txt 來創建工程。
三、手動配置CMakeLists.txt
你也可以自己手動配置 CMakeLists.txt。
首先,我們還是要使用 QtCreator 創建項目:helloworld。項目中的文件列表如下:項目雖小,五臟俱全,該有的文件都有了(.h .cpp .qrc .ui .pro .png)。我把 .png 文件和 .qrc 文件放在了一個新建的 resources 資源文件夾中。
├── helloworld.pro
├── helloworld.pro.user
├── main.cpp
├── resources
│ ├── ico.png
│ └── resources.qrc
├── widget.cpp
├── widget.h
└── widget.ui
該項目中唯一需要添加代碼的地方是 widget.cpp 文件,因為我們需要添加一個圖標:
#include "widget.h"
#include "ui_widget.h"
#include <QIcon>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 窗體標題
this->setWindowTitle("Qt5.1 窗體應用");
// 窗體 ICO 圖片,如圖不起別名,后綴直接寫圖片全名。
this->setWindowIcon(QIcon(":/new/prefix1/ico.png"));
}
Widget::~Widget()
{
delete ui;
}
接着在項目文件夾中手動創建一個 CMakeLists.txt 文件,添加到工程中,其內容如下:
#設置cmake版本號
cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
#設置工程名稱(看情況修改)
project(helloworld)
# 添加C++11(非必須)
set(CMAKE_CXX_STANDARD 11)
#打開全局moc
set(CMAKE_AUTOMOC ON)
#打開全局uic
set(CMAKE_AUTOUIC ON)
#打開全局rcc,如果沒有使用qrc,此句可以去掉
set(CMAKE_AUTORCC ON)
#設置工程包含當前目錄,使用*.ui文件時,需要加上這句,否則找不到頭文件
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#查找需要的Qt庫文件,最好每一個庫都要寫,Qt也會根據依賴關系自動添加
find_package(Qt5 REQUIRED Widgets)
#創建工程文件
add_executable(helloworld main.cpp widget.cpp widget.h widget.ui resources/resources.qrc)
#添加Qt5依賴項
target_link_libraries(helloworld Qt5::Widgets)
這是 CMakeLists.txt 編寫的第一種方法,全局控制:從 CMake 全局入手控制文件生成,非常簡便!后面會介紹另外兩種方法。
最后的項目組成如下:
├── CMakeLists.txt
├── CMakeLists.txt.user
├── helloworld.pro
├── helloworld.pro.user
├── main.cpp
├── resources
│ ├── ico.png
│ └── resources.qrc
├── widget.cpp
├── widget.h
└── widget.ui
關閉之前創建的 helloworld 項目,然后用 QtCreator 直接打開 CMakeLists.txt 文件,一開始會彈出一個 configure 窗口,直接 configure 就可以實現項目配置,然后 cmake 該工程,就會添加 main.cpp、widget.h 等源文件到工程中。最后構建運行該項目報錯:
F:\Project\CMake\helloworld\widget.cpp:-1: error: undefined reference to `vtable for Widget'
在網上搜索發現,跟 Qt 的 moc 機制有關,也就是“元對象編譯器”,與之相關需要定義的宏 Q_OBJECT,與 CMakeLists.txt 中的set(CMAKE_AUTOMOC ON)
沖突,解決辦法就是注釋宏 Q_OBJECT。
moc 全稱是 Meta-Object Compiler,也就是“元對象編譯器”。Qt 程序在交由標准編譯器編譯之前,先要使用 moc 分析 C++ 源文件。如果它發現在一個頭文件中包含了宏 Q_OBJECT,則會生成另外一個 C++ 源文件。這個源文件中包含了 Q_OBJECT 宏的實現代碼
修改后運行成功,如下圖所示:
四、擴展:CMakeLists.txt 編寫的另外兩種方法
上面介紹了 CMakeLists.txt 編寫的第一種方法,全局控制,下面介紹另兩種方法。
方法二、目標控制
#設置cmake版本號
cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
#設置工程名稱(看情況修改)
set(project_name helloworld)
project(${project_name})
# 添加C++11(非必須)
set(CMAKE_CXX_STANDARD 11)
#查找需要的Qt庫文件,最好每一個庫都要寫,Qt也會根據依賴關系自動添加
find_package(Qt5 REQUIRED Widgets)
#設置目標名稱
set(target_name ${project_name})
#創建工程文件
add_executable(${target_name} main.cpp widget.cpp widget.h widget.ui resources/resources.qrc)
#添加Qt5依賴項
target_link_libraries(${target_name} Qt5::Widgets)
#設置目標關聯的*.h, *.cpp 使用 Qt moc進行編譯
set_target_properties(${target_name} PROPERTIES AUTOMOC ON)
#設置目標關聯的*.ui 使用 Qt uic進行編譯
set_target_properties(${target_name} PROPERTIES AUTOUIC ON)
#設置目標關聯的*.qrc 使用 Qt uic進行編譯
set_target_properties(${target_name} PROPERTIES AUTORCC ON)
#跳過不需要使用moc編譯的文件。如果覺得麻煩此句可以不寫,automoc能根據*.h,*.cpp代碼里面的宏(Q_OBJECT;Q_GADGET;Q_NAMESPACE)自動判斷是否需要使用moc
set_source_files_properties(main.cpp PROPERTIES SKIP_AUTOMOC ON)
使用該方法,從目標(可執行或者庫)入手控制文件生成,可控力度更細一些。
上述兩種方法:
使用 CMake 官方 Auto 方法生成的 VS 工程,主要會修改三個地方:
- 會將一個 mocs_compilation.cpp 添加到工程里面
- 【附件包含路徑】中會添加一句
TestWindow_autogen\include_xxx
- 【預先生成事件】中會添加預處理命令,源文件修改后會自動重新生成 moc_xxx.cpp 文件
方法三、單個文件控制
#設置cmake版本號
cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)
#設置工程名稱(看情況修改)
set(project_name helloworld)
project(${project_name})
# 添加C++11(非必須)
set(CMAKE_CXX_STANDARD 11)
#查找需要的Qt庫文件,最好每一個庫都要寫,Qt也會根據依賴關系自動添加
find_package(Qt5 REQUIRED Widgets)
#包含當前路徑,使用*.ui文件時,需要加上這句,否則找不到頭文件
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#需要生成的moc文件,輸出文件名稱放在變量 mocfiles中,必須在find QT5 package才能調用
qt5_wrap_cpp(mocfiles widget.h)
source_group("moc" FILES ${mocfiles})
#需要生成的ui文件,必須在find QT5 package才能調用
qt5_wrap_ui(uifiles widget.ui)
#打開全局rcc,如果沒有使用qrc,此句可以去掉
set(CMAKE_AUTORCC ON)
#設置目標名稱
set(target_name ${project_name})
#添加生成的moc文件到目標中
add_executable(${target_name} main.cpp widget.cpp widget.h widget.ui resources/resources.qrc ${mocfiles})
target_link_libraries(${target_name} Qt5::Widgets)
該方法從 源文件 入手控制文件生成,要稍微麻煩一些。
CMake 生成 VS 工程后,將 mainwindow.ui 的預處理命令放在 mainwindow.ui 屬性配置中,而 mainwindow.h 的預處理命令放在CMake Rules/moc_mainwindow.cpp.rule
的屬性配置中。源文件修改后,目標文件會自動生成。
參考: