325 lines
13 KiB
Markdown
325 lines
13 KiB
Markdown
# CMake 使用
|
||
|
||
## 概述
|
||
|
||
cmake的使用围绕着 编译选项 头文件 库文件搜索路径,需要编译哪些源文件,生成动态库,静态库和可执行文件
|
||
|
||
### cmake 命令
|
||
|
||
```shell
|
||
# 配置阶段
|
||
-B # 构建生成的文件存放目录
|
||
-S # CMakeLists.txt 文件所在目录
|
||
-G # 所选工具集 可以通过--help 查看支持哪些工具集
|
||
# 如果需要传递给cmake参数,-Dxxxx=xxx
|
||
# 编译
|
||
--build # 后面就是构建生成的文件存放目录
|
||
--install # 安装 后面是建生成的文件存放目录 --prefix 安装路径
|
||
```
|
||
|
||
### cmake方法
|
||
|
||
工程相关的方法看下面的例子
|
||
|
||
```cmake
|
||
set(key value) #定义变量 也可以拼接变量
|
||
# 使用时通过 ${key} 取值
|
||
set(变量名1 ${xxx1} ${xxx2} ...)
|
||
|
||
list(APPEND <list> [<element> ...])
|
||
# list 也可以移除
|
||
list(REMOVE_ITEM <list> <value> [<value> ...])
|
||
|
||
include(path/xx.cmake) # 加载指定路径下的.cmake文件,并执行其中的命令
|
||
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
|
||
# source_dir:指定了CMakeLists.txt源文件和代码文件的位置,其实就是指定子目录
|
||
# binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
|
||
# EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的ALL目标里,并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。
|
||
```
|
||
|
||
### cmake中常见的预设变量
|
||
|
||
下面的列表中为一些CMake中常用的宏:
|
||
|
||
| 宏 | 含义 |
|
||
| ------------------------ | ------------------------------------------------------------ |
|
||
| CMAKE_CURRENT_SOURCE_DIR | 当前处理的CMakeLists.txt所在的路径 |
|
||
| CMAKE_CURRENT_BINARY_DIR | target 编译目录 |
|
||
| PROJECT_SOURCE_DIR | 使用cmake命令后紧跟的目录,一般是工程的根目录 |
|
||
| EXECUTABLE_OUTPUT_PATH | 重新定义目标二进制可执行文件的存放位置 |
|
||
| LIBRARY_OUTPUT_PATH | 重新定义目标链接库文件的存放位置 |
|
||
| PROJECT_NAME | 返回通过PROJECT指令定义的项目名称 |
|
||
| CMAKE_BINARY_DIR | 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目录的路径 |
|
||
| | |
|
||
| | |
|
||
|
||
```cmake
|
||
# 当前处理的 CMakeLists.txt 文件所在的路径
|
||
message("CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")
|
||
# 顶层 CMakeLists.txt 文件所在的路径(源代码根目录)
|
||
message("CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
|
||
# 顶层构建目录路径
|
||
message("CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
|
||
# 当前处理的 CMakeLists.txt 所在的源代码目录路径
|
||
message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
|
||
# 当前处理的 CMakeLists.txt 文件的构建目录路径
|
||
message("CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")
|
||
# 项目名称(由顶层 project() 定义)
|
||
message("CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}")
|
||
# CMake 可执行文件路径
|
||
message("CMAKE_COMMAND: ${CMAKE_COMMAND}")
|
||
# C 编译器路径
|
||
message("CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}")
|
||
# C++ 编译器路径
|
||
message("CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}")
|
||
# 安装路径前缀
|
||
message("CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
|
||
CMAKE_CXX_STANDARD # c++ 使用标准
|
||
CMAKE_C_STANDARD # C语言标准
|
||
```
|
||
|
||
### 日志
|
||
|
||
在CMake中可以用用户显示一条消息,该命令的名字为message:
|
||
|
||
```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 错误, 终止所有处理过程
|
||
|
||
CMake的命令行工具会在stdout上显示STATUS消息,在stderr上显示其他所有消息。CMake的GUI会在它的log区域显示所有消息。
|
||
|
||
CMake警告和错误消息的文本显示使用的是一种简单的标记语言。文本没有缩进,超过长度的行会回卷,段落之间以新行做为分隔符。
|
||
|
||
```cmake
|
||
# 输出一般日志信息
|
||
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
|
||
# 输出警告信息
|
||
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
|
||
# 输出错误信息
|
||
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")
|
||
```
|
||
|
||
|
||
|
||
#### 输出一般日志信息
|
||
message(STATUS "source path: ${PROJECT_SOURCE_DIR}")
|
||
#### 输出警告信息
|
||
message(WARNING "source path: ${PROJECT_SOURCE_DIR}")
|
||
#### 输出错误信息
|
||
message(FATAL_ERROR "source path: ${PROJECT_SOURCE_DIR}")
|
||
|
||
### 构建基本工程
|
||
|
||
```shell
|
||
# 文件
|
||
│ CMakeLists.txt
|
||
│ main.cpp
|
||
│ tools.cpp
|
||
│
|
||
└─include
|
||
tools.h
|
||
```
|
||
|
||
生成可执行文件
|
||
|
||
```cmake
|
||
cmake_minimum_required(VERSION 3.14)# 指定使用的 cmake 的最低版本
|
||
# 定义工程名称,并可指定工程的版本、工程描述、web主页地址、支持的语言(默认情况支持所有语言),如果不需要这些都是可以忽略的,只需要指定出工程名字即可
|
||
project(tools)
|
||
set(CMAKE_CXX_STANDARD 17)
|
||
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_LIST_DIR}/bin) # 设置可执行文件的输出路径
|
||
|
||
link_directories("C:/thirdpart/lib") # 搜索库文件目录 每一条命令只能包含一个路径
|
||
# ${CMAKE_CURRENT_LIST_DIR} 变量是最常用的变量之一,表示当前处理的CMakeLists.txt所在的路径,也就是项目的根目录
|
||
include_directories(${CMAKE_CURRENT_LIST_DIR}/include) # 搜索头文件目录 每一条命令只能包含一个路径
|
||
|
||
file(GLOB headers include/*.h*) # 搜索include文件夹下所有h hpp头文件
|
||
file(GLOB src ${CMAKE_CURRENT_LIST_DIR}/*.cpp) # 将当前目录下所有cpp文件后缀的源文件, 赋值给src
|
||
# GLOB 参数 搜索给定路径下的所有匹配的文件,并将文件列表存储在变量中,不会递归搜索子目录,参数 GLBO_RECURSE 会递归搜索子目录
|
||
# aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/ src) # 将指定目录下的所有源文件的文件名存入变量名 src 中 与file功能一致
|
||
|
||
add_executable(${PROJECT_NAME} ${src} ${headers}) # 生成可执行文件 ${PROJECT_NAME} 为当前项目的名称 ${src} 为源文件列表
|
||
target_link_libraries(${PROJECT_NAME} PUBLIC datachannel) # 链接库 以公有的方式链接 datachannel 库
|
||
|
||
# add_library(${PROJECT_NAME} SHARED ${src} ${headers}) # 生成动态库
|
||
# add_library(${PROJECT_NAME} STATIC ${src} ${headers}) # 生成静态库
|
||
|
||
|
||
# 构建生成vs工程
|
||
# cd root/ cmake -B build -G "Visual Studio 17 2022" -S .
|
||
# cmake --build build --config Debug
|
||
|
||
|
||
```
|
||
|
||
生成的vs工程
|
||
|
||
![image-20241109130700149](/assert/image-20241109130700149.png)
|
||
|
||
可以看出来,只有将源文件加载到了工程中,看不到头文件
|
||
|
||
需要搜索头文件,并加入生成可执行文件的依赖
|
||
|
||
```cmake
|
||
file(GLOB headers include/*.h*) # 搜索include文件夹下所有h hpp头文件
|
||
add_executable(${PROJECT_NAME} ${src} ${headers}) # 生成可执行文件不仅需要源文件还需要头文件 tip:只是针对vs
|
||
```
|
||
|
||
再次构建编译后, 可以看到头文件也加载进来了
|
||
|
||
![image-20241109131136773](/assert/image-20241109131136773.png)
|
||
|
||
针对构建特定目标,需要加载进来特定头文件等,而非全局
|
||
|
||
```cmake
|
||
# target_XXXXX
|
||
target_link_directories(目标名字 PUBLIC | PRIVAET | INTERFACE xxxx) # 访问权限,默认为PUBLIC
|
||
target_include_directories()
|
||
target_link_libraries()
|
||
|
||
# 就是跟下面的功能一样,只不过上面是针对特定目标
|
||
link_directories()
|
||
include_directories()
|
||
link_libraries()
|
||
|
||
#eg:
|
||
add_library(test1 STATIC test1.cpp)
|
||
target_include_directories(test1 PUBLIC include/test1)
|
||
|
||
add_library(test2 STATIC test2.cpp)
|
||
target_include_directories(test1 PUBLIC include/test2)
|
||
# test1 需要用到include/test1中的头文件,而test2依赖他所需要的头文件
|
||
# 当然不管需要不需要 直接全部用include_directories 全部加载进来也是可以的
|
||
|
||
# target_link_libraries 是更推荐的方式,因为它允许更精确的控制和管理链接库的依赖,特别是在大型项目中,它能够避免全局设置可能带来的问题。
|
||
# link_libraries 虽然简单,但在复杂的项目中可能会导致意外的问题,通常适用于简单的项目或临时设置。
|
||
# 建议在 CMake 项目中优先使用 target_link_libraries
|
||
```
|
||
|
||
+ PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
|
||
+ PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
|
||
+ INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。
|
||
|
||
### 流程控制
|
||
|
||
在 CMake 的 CMakeLists.txt 中也可以进行流程控制,也就是说可以像写 shell 脚本那样进行条件判断和循环。
|
||
|
||
#### 条件判断
|
||
|
||
```cmake
|
||
# 关于条件判断其语法格式如下:
|
||
if(<condition>)
|
||
# <commands>
|
||
elseif(<condition>) # 可选快, 可以重复
|
||
#<commands>
|
||
else() # 可选快
|
||
#<commands>
|
||
endif()
|
||
# 在进行条件判断的时候,如果有多个条件,那么可以写多个elseif,最后一个条件可以使用else,但是开始和结束是必须要成对出现的,分别为:if和endif。
|
||
```
|
||
基本表达式
|
||
|
||
```cmake
|
||
# 基本表达式
|
||
if(<expression>) # NOT AND OR
|
||
#]=]
|
||
# 如果是基本表达式,expression 有以下三种情况:常量、变量、字符串。
|
||
# 如果是1, ON, YES, TRUE, Y, 非零值,非空字符串时,条件判断返回True
|
||
# 如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串时,条件判断返回False
|
||
# ]=]
|
||
# 逻辑判断
|
||
if( <condition>)
|
||
# 其实这就是一个取反操作,如果条件condition为True将返回False,如果条件condition为False将返回True。
|
||
# 如果cond1和cond2两个条件中至少有一个为True,返回True,如果两个条件都为False则返回False。
|
||
```
|
||
比较
|
||
|
||
```cmake
|
||
if(<variable|string> LESS <variable|string>)
|
||
if(<variable|string> GREATER <variable|string>)
|
||
if(<variable|string> EQUAL <variable|string>)
|
||
if(<variable|string> LESS_EQUAL <variable|string>)
|
||
if(<variable|string> GREATER_EQUAL <variable|string>)
|
||
# LESS:如果左侧数值小于右侧,返回True
|
||
# GREATER:如果左侧数值大于右侧,返回True
|
||
# EQUAL:如果左侧数值等于右侧,返回True
|
||
# LESS_EQUAL:如果左侧数值小于等于右侧,返回True
|
||
# GREATER_EQUAL:如果左侧数值大于等于右侧,返回True
|
||
|
||
|
||
# 基于字符串的比较
|
||
|
||
if(<variable|string> STRLESS <variable|string>)
|
||
if(<variable|string> STRGREATER <variable|string>)
|
||
if(<variable|string> STREQUAL <variable|string>)
|
||
if(<variable|string> STRLESS_EQUAL <variable|string>)
|
||
if(<variable|string> STRGREATER_EQUAL <variable|string>)
|
||
# STRLESS:如果左侧字符串小于右侧,返回True
|
||
# STRGREATER:如果左侧字符串大于右侧,返回True
|
||
# STREQUAL:如果左侧字符串等于右侧,返回True
|
||
# STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回True
|
||
#TRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回True
|
||
```
|
||
|
||
#### 循环
|
||
|
||
在 CMake 中循环有两种方式,分别是:foreach和while。
|
||
使用 foreach 进行循环,语法格式如下:
|
||
|
||
```cmake
|
||
foreach(<loop_var> <items>)
|
||
# <commands>
|
||
endforeach()
|
||
```
|
||
|
||
通过foreach我们就可以对items中的数据进行遍历,然后通过loop_var将遍历到的当前的值取出,在取值的时候有以下几种用法:
|
||
|
||
```cmake
|
||
# 方法1
|
||
foreach(<loop_var> RANGE <stop>)
|
||
# RANGE:关键字,表示要遍历范围
|
||
# stop:这是一个正整数,表示范围的结束值,在遍历的时候从 0 开始,最大值为 stop。
|
||
# loop_var:存储每次循环取出的值
|
||
cmake_minimum_required(VERSION 3.2)
|
||
project(test)
|
||
# 循环
|
||
foreach(item RANGE 10)
|
||
message(STATUS "当前遍历的值为: ${item}" )
|
||
endforeach()
|
||
|
||
# 方法二
|
||
foreach(<loop_var> RANGE <start> <stop> [<step>])
|
||
#[=[
|
||
这是上面方法1的加强版,我们在遍历一个整数区间的时候,除了可以指定起始范围,还可以指定步长。
|
||
RANGE:关键字,表示要遍历范围
|
||
start:这是一个正整数,表示范围的起始值,也就是说最小值为 start
|
||
stop:这是一个正整数,表示范围的结束值,也就是说最大值为 stop
|
||
step:控制每次遍历的时候以怎样的步长增长,默认为1,可以不设置
|
||
loop_var:存储每次循环取出的值
|
||
#]=]
|
||
cmake_minimum_required(VERSION 3.2)
|
||
project(test)
|
||
|
||
foreach(item RANGE 10 30 2)
|
||
message(STATUS "当前遍历的值为: ${item}" )
|
||
endforeach()
|
||
```
|
||
|
||
|
||
|
||
## 工程实践
|
||
|
||
### 导出安装
|