1. Basic Cmake Based Project
# Qt对cmake版本的最小要求(但测试发现低一点的版本似乎也没问题)
cmake_minimum_required(VERSION 3.16.0)
# 项目命名
# VERSION 1.0.0 LANGUAGES CXX: 是可选的
project(helloworld VERSION 1.0.0 LANGUAGES CXX)
# 如果采用非Qt Creator开发,需要通过告知Qt的安装路径,建议把Qt的安装路径设置到环境变量
# 例如:QT_DIR=D:\Qt\6.1.2\msvc2019_64
set(CMAKE_PREFIX_PATH $ENV{QT_DIR})
# 有些项目会动态生成头文件,项目中需要引入它,因此需要将output目录也include进来
# 等同于INCLUDE_DIRECTORY(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Qt6 对C++版本推荐至少17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Qt特有的编译器需要打开,默认是关闭的
set(CMAKE_AUTOMOC ON) # Meta-Object Compiler
set(CMAKE_AUTORCC ON) # Resource Compiler
set(CMAKE_AUTOUIC ON) # User Interface Compiler
# 寻找Qt的库
# Qt6 COMPONENTS Widgets:寻找Qt库中的Widget模块
# REQUIRED: 意味着找不到报错并不会继续下去
find_package(Qt6 COMPONENTS Core Qml Quick LinguistTools REQUIRED)
# 集成源码以及资源并打包
set(TS_FILES TestApp_zh_CN.ts TestApp_en_US.ts)
aux_source_directory(src SRC_LIST)
qt6_add_resources(QML_QRC qml_module_a.qrc qml_module_b.qrc)
set(PROJECT_SOURCES ${SRC_LIST} ${TS_FILES} ${QML_QRC})
# 这里如果不加WIN32,会导致编译的可执行文件运行时候会同时弹出一个命令行终端
# 这是Windows的特性,对于其它平台得去掉WIN32
add_executable(${CMAKE_PROJECT_NAME} WIN32 ${PROJECT_SOURCES})
qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
# cmake本身有四种编译模式:`Debug`, `Release`, `RelWithDebInfo`, `MinSizeRel`
# 此操作将`Debug`和`RelWithDebInfo`归类于QML的debug,即这两种模式下QML运行会保留debug信息
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
# 链接库到当前项目
# PRIVATE:项目私有内部链接,只有在开发Library对外公开时候才会使用PUBLIC
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE
Qt6::Core
Qt6::Qml
Qt6::Quick)
# 加入新qml文件能自动扫描到并集成到项目
qt_import_qml_plugins(${CMAKE_PROJECT_NAME})
2. Executable VS Library
# 将${SRC_LIST}将编译成可执行文件,如果没有main函数会报错
add_executable(${CMAKE_PROJECT_NAME} ${SRC_LIST})
# 将${SRC_LIST}编译为library,library的类型可选择,默认是静态库
add_library(${CMAKE_PROJECT_NAME} [STATIC|SHARED|MODULE] ${SRC_LIST})
3. Every module has its own CMakeList.txt in its folder
3.1 不推荐的做法:
aux_source_directory(. DIR_SRCS1)
aux_source_directory(. DIR_SRCS2)
aux_source_directory(. DIR_SRCS3)
add_executable(${CMAKE_PROJECT_NAME} ${DIR_SRCS1} ${DIR_SRCS2} ${DIR_SRCS3})
这种做法会导致项目里即便改了一处代码,也会编译所有代码,导致编译时间较长,不能很好利用增量编译,再说C/C++本身编译就很慢
3.2 推荐的做法
以下以一个开源项目live555
改版成为cmake项目作为推荐的项目代码组织结构的案例,虽然最终目标是编译成Library给外部使用,但内部同时也包含了打包成可执行文件的模块(mediaServer):
├── CMakeLists.txt
├── BasicUsageEnvironment
│ └── CMakeLists.txt
│ └── BasicHashTable.cpp
│ └── xxx.cpp
│ └── include
│ └── BasicHashTable.hh
│ └── xxx.hh
├── groupsock
│ └── CMakeLists.txt
│ └── GroupEId.cpp
│ └── xxx.cpp
│ └── include
│ └── GroupEId.hh
│ └── xxx.hh
├── liveMedia
│ └── CMakeLists.txt
│ └── AC3AudioRTPSink.cpp
│ └── xxx.cpp
│ └── include
│ └── GroupEId.hh
│ └── xxx.hh
├── mediaServer
│ └── CMakeLists.txt
│ └── DynamicRTSPServer.cpp
│ └── xxx.cpp
└── UsageEnvironment
└── CMakeLists.txt
└── HashTable.cpp
└── xxx.cpp
└── include
└── Boolean.hh
└── xxx.hh
其中,
BasicUsageEnvironment
,groupsock
,liveMedia
,UsageEnvironment
都是live555项目的子模块,mediaServer
是集成所有子模块打包成为可执行文件的部分。
作为项目入口,推荐的CMakeList.txt可以如下:
cmake_minimum_required(VERSION 3.15)
project(live555 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# add all sub folders as modules
ADD_SUBDIRECTORY(UsageEnvironment)
ADD_SUBDIRECTORY(BasicUsageEnvironment)
ADD_SUBDIRECTORY(groupsock)
ADD_SUBDIRECTORY(liveMedia)
ADD_SUBDIRECTORY(mediaServer)
模块内部CMakeList.txt推荐如下:
# 这里指定当前模块名,这里推荐用文件名作为模块名
project(BasicUsageEnvironment)
# 因为当前模块cpp里使用里其他模块的头文件,因此需要把它们include进来
include_directories(../UsageEnvironment/include)
include_directories(../groupsock/include)
# 当前模块的头文件肯定要include进来
include_directories(./include)
# 当前模块下的cpp跟CMakeList.txt在同级目录
aux_source_directory(./ SRC_LIST)
# 当前只是模块,最终需要把所有的模块合并构建,因此当前需要指定编译对象为STATIC
# ${PROJECT_NAME}的值即为当前模块名,需要注意的是不能用${CMAKE_PROJECT_NAME},因为那是跟目录下CMakeList.txt指定的名字,那是整个项目的名字,即:live555
add_library(${PROJECT_NAME} STATIC ${SRC_LIST})
作为目标是编译为可执行文件的CMakeList.txt如下:
project(mediaServer)
include_directories(../UsageEnvironment/include)
include_directories(../groupsock/include)
include_directories(../liveMedia/include)
include_directories(../BasicUsageEnvironment/include)
# 当前目录下也有代码
aux_source_directory(. SRC_LIST)
# 当前模块是main入口,最终目标是编译出live555的可执行文件,因此不再用add_library()
add_executable(${PROJECT_NAME} ${SRC_LIST})
# 链接所有其他模块到当前模块
target_link_libraries(${PROJECT_NAME} PRIVATE
liveMedia
groupsock
BasicUsageEnvironment
UsageEnvironment)
4. 强制以Debug
, Release
, RelWithDebInfo
, MinSizeRel
其中一种作为编译方式
方法一:
在工程build目录下执行 cmake .. -DCMAKE_BUILD_TYPE=Debug|Release|MinSizeRel|RelWithDebInfo
方法二:
或者在顶级CMakeList.txt里加入: set(CMAKE_BUILD_TYPE Debug|Release|MinSizeRel|RelWithDebInfo)
5. CMake高频常用变量
变量的引用方式是使用${}
,在IF中,不需要使用这种方式,直接使用变量名亦可
自定义变量:SET(OBJ_NAME xxxx)
,调用自定义变量: ${OBJ_NAME}
设置环境变量: SET(ENV{NAME} value)
, 需要注意的是这里ENV
没有$
; 调用环境变量: $ENV{NAME}
CMAKE的常用变量:
变量 | 意义 |
---|---|
CMAKE_SOURCE_DIR |
工程项目跟目录 |
CMAKE_CURRENT_SOURCE_DIR |
CMakeList.txt所在的目录 |
CMAKE_MODULE_PATH |
如果工程复杂,可能需要编写一些cmake模块,这里通过SET指定这个变量 |
LIBRARY_OUTPUT_DIR |
库最终存放目录 |
BINARY_OUTPUT_DIR |
可执行的最终存放目录 |
PROJECT_NAME |
当前CMakeList.txt里设置的project_name |
CMAKE_PROJECT_NAME |
项目跟目录配置的project_name |
6. 在CMake里区分操作系统
if(CMAKE_SYSTEM_NAME MATCHES "Linux") // 注意区分大写
message(STATUS "Linux platorm!")
elseif(CMAKE_SYSTEM_NAME MATCHES "Windows")
if(CMAKE_CL_64)
message(STATUS "Windows Win64 platform!")
else()
message(STATUS "Windows Win32 platform!")
endif(CMAKE_CL_64)
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
message(STATUS "FreeBSD platform!")
else()
message(STATUS "other platform!")
endif(CMAKE_SYSTEM_NAME MATCHES "Linux")
简化版亦可:
if (WIN32)
message(STATUS "Now is windows")
elseif (APPLE)
message(STATUS "Now is Apple systens.")
elseif (UNIX)
message(STATUS "Now is UNIX-like OS's.")
endif ()
7. 在CMake里打印信息
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
(无) = 重要消息
STATUS = 非重要消息
WARNING = CMake 警告, 会继续执行
AUTHOR_WARNING = CMake 警告 (dev), 会继续执行
SEND_ERROR = CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR = CMake 错误, 终止所有处理过程
一般用于排错打印日志,或者打印编译过程信息及步骤