赞
踩
# 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})
# 将${SRC_LIST}将编译成可执行文件,如果没有main函数会报错
add_executable(${CMAKE_PROJECT_NAME} ${SRC_LIST})
# 将${SRC_LIST}编译为library,library的类型可选择,默认是静态库
add_library(${CMAKE_PROJECT_NAME} [STATIC|SHARED|MODULE] ${SRC_LIST})
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++本身编译就很慢
以下以一个开源项目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)
方法一:
// 在工程build目录下执行
cmake .. -DCMAKE_BUILD_TYPE=Debug|Release|MinSizeRel|RelWithDebInfo
方法二:
// 或者在顶级CMakeList.txt里加入:
set(CMAKE_BUILD_TYPE Debug|Release|MinSizeRel|RelWithDebInfo)
变量的引用方式是使用${},在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
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 ()
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 错误, 终止所有处理过程
一般用于排错打印日志,或者打印编译过程信息及步骤
在 CMake 中,target_include_directories() 命令用于为指定的目标(如库或可执行文件)添加包含目录。这些包含目录会在编译目标时告诉编译器去哪里查找头文件。
target_include_directories(${PROJECT_NAME}
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
PUBLIC: 这表示以下指定的目录是公共的,意味着它们不仅对该目标有用,而且对其依赖项也有用。当其他目标链接到这个目标时,它们也会继承这些包含目录。
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>: 在构建目标时,这个目录将被添加到包含路径中。${CMAKE_CURRENT_SOURCE_DIR} 是当前处理的 CMakeLists.txt 文件所在的目录,因此这个路径指向项目源代码中的 include 目录。
$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>: 同样,在构建目标时,这个目录也将被添加到包含路径中。${PROJECT_BINARY_DIR} 是构建过程中生成的文件(如编译的输出)所在的目录,因此这个路径指向构建输出目录中的 include 目录。
$<INSTALL_INTERFACE:include>: 当目标被安装时,这个目录将被添加到包含路径中。这意味着,如果其他项目安装了这个目标并链接到它,它们将在 include 目录下查找头文件。
PRIVATE: 这表示以下指定的目录是私有的,仅对该目标有用,不会传递给它的依赖项。
${CMAKE_CURRENT_SOURCE_DIR}/include: 在构建目标时,这个目录将被添加到包含路径中,并且只对该目标可见。这意味着,尽管它位于源代码目录中,但它不会传递给任何链接到这个目标的其他目标。
总结起来,这些命令确保了在构建和安装项目时,编译器能够找到必要的头文件。公共目录会被传递给链接到该项目的其他项目,而私有目录则不会。这种做法有助于组织和管理项目的依赖关系,并确保在不同环境中构建的一致性。
execute_process( # 执行一个子进程
COMMAND git rev-parse --short HEAD # 命令
OUTPUT_VARIABLE ${PROJECT_NAME}_COMMIT # 输出字符串存入变量
OUTPUT_STRIP_TRAILING_WHITESPACE # 删除字符串尾的换行符
ERROR_QUIET # 对执行错误静默
WORKING_DIRECTORY # 执行路径
${CMAKE_CURRENT_SOURCE_DIR}
)
详细解释:
COMMAND git rev-parse --short HEAD: 这是要执行的命令本身。git rev-parse --short HEAD 会返回当前 Git 仓库的 HEAD 提交的简短哈希值(通常是前七个字符)。
OUTPUT_VARIABLE ${PROJECT_NAME}_COMMIT: 这告诉 CMake 将命令的输出存储在一个变量中。${PROJECT_NAME}_COMMIT 是一个变量名,${PROJECT_NAME} 是 CMake 预定义的变量,代表当前项目的名称。因此,如果项目名称是 MyProject,那么输出变量将会是 MyProject_COMMIT。
OUTPUT_STRIP_TRAILING_WHITESPACE: 这个选项告诉 CMake 在将输出存储到变量之前去除任何尾随的空白字符,包括换行符。这对于确保变量中只包含纯哈希值非常重要。
ERROR_QUIET: 这个选项告诉 CMake 如果命令执行失败(例如,如果 Git 没有安装或当前目录不是一个 Git 仓库),则不要显示任何错误消息。这通常用于在构建过程中避免不必要的警告或错误,特别是当 Git 信息不是构建所必需的时候。
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}: 这指定了命令执行的工作目录。${CMAKE_CURRENT_SOURCE_DIR} 是一个 CMake 预定义的变量,表示当前处理的 CMakeLists.txt 文件所在的目录。设置工作目录对于确保命令在正确的上下文中执行很重要,特别是当构建系统包含多个子目录和源文件时。
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
这不是一个常规的 add_library 调用,而是使用了 ALIAS 关键字来创建一个别名目标。别名目标是一种特殊的 CMake 目标,它并不实际构建任何东西,而是简单地提供了对另一个已存在目标的引用。
这里的 ${PROJECT_NAME}::${PROJECT_NAME} 创建了一个名为 <项目名>::<项目名> 的别名目标,该别名目标简单地引用了 ${PROJECT_NAME} 所代表的原始目标。${PROJECT_NAME} 通常是在 CMakeLists.txt 文件的顶部通过 project() 命令设置的项目的名称。
别名目标在几个场景中可能很有用:
目标导入:当你在一个 CMake 项目中使用其他项目生成的库时,这些库可能使用不同的命名约定或命名空间。通过使用别名,你可以提供一个统一的、符合你项目命名约定的目标名称。
兼容性:随着时间的推移,项目的构建系统可能会发生变化。通过创建别名,你可以在不更改引用该目标的其他代码的情况下,更改底层实现的目标。
条件构建:在某些情况下,你可能希望在满足特定条件时构建不同的库,但希望提供一个一致的目标名称给其他代码引用。通过使用别名,你可以在不更改引用代码的情况下切换底层实现。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。