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, 它还引用了OpenCV
和VTK
, 另外, 还使用了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)
find_package(Qt6 REQUIRED COMPONENTS Widgets OpenGLWidgets) qt_standard_project_setup()
message("Link OpenCV build library via OpenCVConfig.cmake")
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)
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_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
|
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的项目构建.