docs/cmake.md
2025-01-15 10:24:25 +08:00

325 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 :非重要消息
+ WARNINGCMake 警告, 会继续执行
+ AUTHOR_WARNINGCMake 警告 (dev), 会继续执行
+ SEND_ERRORCMake 错误, 继续执行,但是会跳过生成的步骤
+ FATAL_ERRORCMake 错误, 终止所有处理过程
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()
```
## 工程实践
### 导出安装