Qt使用CMake编写工程

长期以来都是使用qmake管理Qt的源代码工程, 从Qt6开始, 官方改用CMake了, 但是我习惯了qmake, 觉得cmake有诸多不便, 仍然是习惯使用qmake. 直到不久前换东家, 新公司使用Qt5.9, 开发环境是Linux, 还没有creator可用, 只能用vscode做编辑器. 编译工具也是使用cmake. 这才不得不认认真真回来钻研一下cmake和qt在cmake下的用法.

不过, 说真的, 以前没有注意到, 如果开发工具不好, 对开发效率真的有着极大的负面作用. 比如说, 我是Qt12开始使用的, 大部分时间是使用Qt5.15版本和Qt6.2版本在Windows下开发. 而现在的公司是Qt5.9, C++编译器是不完全的C++14, 还只有VSCode可用. 不知道谁写的CMakefile, vscode解析就会失败, 到现在我也没有找到原因. 唯一的好处是确实锻炼了API的记忆能力, 不像以前, 只要几个大概, 自然IDE会给你联想.

基本的CMake工程

Qt的帮助中有比较详细的说明, 因为比较老, 没有新的IDE使用的那么花哨, 更为朴素, 更便于了解. 下面只讨论包含子项目和外部库的情况.

主项目

下面是一个名为practices的项目的顶层CMakeLists.txt的内容.
除了Qt, 它还引用了OpenCVVTK, 另外, 还使用了OpenGLWidgets.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
cmake_minimum_required(VERSION 3.16)
project(practices VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Qt配置:
find_package(Qt6 REQUIRED COMPONENTS Widgets OpenGLWidgets)
qt_standard_project_setup()

# ======================================================
# OpenCV配置:
##[[
message("Link OpenCV build library via OpenCVConfig.cmake")
#set(OpenCV_DIR "C:/libs/opencv/opencv4100/build/x64/vc16/lib")
set(OpenCV_DIR "C:/libs/opencv/opencv3416/build/x64/vc15/lib")
message("OpenCV_DIR : " ${OpenCV_DIR})
add_definitions(-D LINK_OPENCV_LIB_AUTO)
find_package(OpenCV REQUIRED)
#message("\n=======================================")
#message("INCLUDE: " ${OpenCV_INCLUDE_DIRS})
#message("LIBS: " ${OpenCV_LIBS})
#foreach(_fn ${OpenCV_LIBS})
# message("lib: " ${_fn})
#endforeach()
#message("=========================================\n")
#子项目都需要添加下面的内容:
#include_directories(sample5 ${OpenCV_INCLUDE_DIRS})
#target_link_libraries(sample5 ${OpenCV_LIBS})
#]]



# ======================================================
# VTK配置:
#[[
find_package(VTK REQUIRED)
if ( VTK_FOUND )
message ( STATUS " VTK_FOUND = ${VTK_FOUND}" )
message ( STATUS " VTK_MAJOR_VERSION = ${VTK_MAJOR_VERSION}" )
message ( STATUS " VTK_MINOR_VERSION = ${VTK_MINOR_VERSION}" )
message ( STATUS " VTK_BUILD_VERSION = ${VTK_BUILD_VERSION}" )
message ( STATUS " VTK_LIBRARY_DIRS = ${VTK_LIBRARY_DIRS}" )
message ( STATUS " VTK_LIBRARIES = ${VTK_LIBRARIES}" )
endif()
]]

#添加子项目
add_subdirectory(src/imageprocessor)

要使用OpenCV, 必须在环境变量OpenCV_DIR中定义指向OpenCV所带的OpenCVConfig.cmake的目录. 如果是使用Windows的预编译版本, 它和OpenCV的lib文件放在一起. 网络上关于Windows下Qt用cmake集成opencv的很少, 并且以讹传讹很多. 实验下来, 最好的办法是前面的, 使用set命令直接定义OpenCV_DIR的方式, 这样, 即使在电脑的环境变量里面定义了OpenCV_DIR, 也可以在这里屏蔽它. 这样, 电脑中有多个OpenCV版本的时候也不会造成影响.

后面的VTK也是同样的做法. 要使find_package方式找VTK, 也需要CMake在VTK_DIR下寻找对应的cmake文件. 在我的电脑里, 这个值是C:\libs\VTK\VTK-9.3.0, 因为我只装了一个版本, 所以就直接使用环境变量了. 如果你的电脑有多个VTK版本, 同样可以用set命令在CMakeLists.txt中定义.

最后, 是使用add_subdirectory()命令来添加子项目. 所有的子项目会自动继承所有父项目中定义的变量.

子项目有两种, 库或者应用程序.

另外, 在上面的例子中是专门针对Qt6的简化. 在Qcreator自己生成的cmake文件中, 是这样判断当前Qt版本的:

1
2
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

它会自动寻找Qt的版本, 并优先选择Qt6. 对我来说, 同时支持两个版本没啥意义, 就只保留了Qt6的.

同样的, 标准生成的文件中还会对Android, Mac等有一堆特殊适配, 这些对我来说都没有用处, 就都删除了.

应用程序子项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

set(projname imageprocessor)

set(PROJECT_SOURCES
impmainwnd.cpp
impmainwnd.h
impmainwnd.ui
main.cpp
zoomableview.h
zoomableview.cpp
)

qt_add_executable(${projname}
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
include_directories(${projname} ${OpenCV_INCLUDE_DIRS})

target_link_libraries(${projname} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets
${OpenCV_LIBS}
)

set_target_properties(${projname} PROPERTIES
WIN32_EXECUTABLE TRUE
)

qt_finalize_executable(${projname})

使用了VTK的子项目

下面是一个使用了VTK的子项目. 这里唯一特殊的是我们使用了预编译头文件. target_precompile_headers. 而这里的用于预编译头文件的文件列表则是在主工程里面定义的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
qt_add_executable(vtkdemo03
main.cpp
mainwindow.h mainwindow.cpp mainwindow.ui
colormapform.h colormapform.cpp colormapform.ui
drawentityform.h drawentityform.cpp drawentityform.ui
vtktools.h vtktools.cpp
medicalform.h medicalform.cpp medicalform.ui
visualizationform.h visualizationform.cpp visualizationform.ui
imageform.h imageform.cpp imageform.ui
)

target_precompile_headers(vtkdemo03 PUBLIC
${PCH_FILES}
)

target_link_libraries(vtkdemo03 PRIVATE Qt6::Widgets
${VTK_LIBRARIES}
)

vtk_module_autoinit(
TARGETS vtkdemo03
MODULES ${VTK_LIBRARIES}
)

set_target_properties(vtkdemo03 PROPERTIES
WIN32_EXECUTABLE ON
MACOSX_BUNDLE ON
)

使用OpenGL的项目

下面是一个使用OpenGL的简单示例项目, 另外它还使用了glm库. glm库需要定义环境变量glm_DIR才能使用find_package(glm ...)的方式. 我在电脑的环境变量中做了定义: glm_DIR=C:\libs\glm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
cmake_minimum_required(VERSION 3.5)

project(chapter052 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(Qt6 REQUIRED COMPONENTS Widgets OpenGLWidgets)
find_package(glm CONFIG REQUIRED)

include_directories(C:/libs/qtcommon/include)

set(PROJECT_SOURCES
main.cpp
widget.cpp
widget.h
)

qt_add_executable(${PROJECT_NAME}
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)

target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets Qt6::OpenGLWidgets glm::glm-header-only)

set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)

qt_finalize_executable(${PROJECT_NAME})

Widget本身很简单, 是QOpenGLWidget的一个派生类, 没啥好说的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Widget : public QOpenGLWidget
//, protected QOpenGLFunctions
, protected QOpenGLFunctions_4_5_Core
{
Q_OBJECT

public:
Widget(QWidget *parent = nullptr);
~Widget();
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;

void init();
GLuint compileShaders();
void printShaderLog(GLuint shader);
void printProgramLog(int prog);
bool checkOpenGLError();

private:
struct Implementation;
QScopedPointer<Implementation> _impl;
};

QTEST

下面是自动创建的一个QTEST项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 使用QTEST的一个项目模板.
#cmake_minimum_required(VERSION 3.16)
#project(MatchTestCase LANGUAGES CXX)

enable_testing()
find_package(Qt6 REQUIRED COMPONENTS Test)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(MatchTestCase tst_matchtestcase.cpp)
add_test(NAME MatchTestCase COMMAND MatchTestCase)

target_link_libraries(MatchTestCase PRIVATE Qt6::Test)

相比其他, 这里的就更为原始了. 完全没有使用任何Qt提供的cmake的函数. 基于这个项目做改造, 就能用于原本不支持cmake的Qt5的项目构建.