自学内容网 自学内容网

cmake的使用

[b站教程](https://www.bilibili.com/video/BV1xa4y1R7vT/)

## 第一章 CMake初体验

### 1. CMake是什么、CMake构建项目的流程

CMake是一个**开源的、跨平台的自动化构建**工具,通过cmake我们可以轻松地管理我们的项目。

- 注意:
    - CMake并不是包管理工具!
    - CMake并不只支持C/C++!
    - 掌握CMake是学习C++必经之路!

### 2. CMake的优点和缺点

- 优点
    - 操作透明而细腻
    - 它专注于现代C++现代化,专注于支持C++现代编译器和工具链
    - 真正的跨平台,支持比如Windows、Linux、macOS、Cygwin(Linux移植到Windows)等
    - 支持生成几乎所有主流IDE的项目
- 缺点
    - 它还在成长
    - CMake它是一门语言,你需要学习

**CMake没有阻碍C++的发展,相反CMake拯救了C++!**

相比其他语言的构建工具,CMake自然要复杂的多,但这不是CMake的问题。相比其他的编程语言,C/C++更关注底层和构建其他的编程语言,因此CMake面临的问题的和其他编程语言是完全不同的!

而且正因为CMake的出现,C++才终于完成了项目跨平台!

### 3. C/C++源文件是怎么生成可执行程序的toolchain流程

1. 预处理 (-E参数 宏替换等)
2. 编译 gcc/msvc/clang (-S参数)
3. 汇编 (-C参数 linux生成.o文件、windows生成.obj文件)
4. 连接 (将多个二进制文件连接生成一个可执行的文件)

### 4. CMake与Makefile、Make的关系

- Makefile并不跨平台,CMake会根据编译器的类型来决定是否生成Makefile,大多数情况下CMake会生成Makefile
- 大型项目不推荐大家手动编写Makefile
- Make工具(类似批处理工具)是通过调用makefile文件中的命令实现编译和链接的,本人极其反感在cmake中使用make命令

### 5. CMake命令行执行流程

![image-20240922225916768](cmake的使用/cmake流程图.png)

1. 编写CMakeLists.txt文件,下面是最基本的配置
    - cmake_minimum_required(VERSION 3.20)#最小版本
    - project(Hello)#项目名
    - add_executable(Hello hello.cpp)#由源文件生成一个可执行的程序
2. cmake -B build
    - 创建一个build并在此目录下生成makefile或其他文件

3. cmake --build 
    - build生成项目

### 6. 在Windows下使用cmake来构建项目

- Windows下的CMake安装
- 官网下载CMake
- https://cmake.org/download/
- 安装完成后输入`cmake --version`查看是否正确安装

![image-20240923132830927](cmake的使用/安装cmake.png)

#### 6.1 Windows下的build system generator

- 默认MSVC (vs2022与vs2019)
- 可以安装 MinGW (gcc与clang)
- cmake参数: `cmake -G <generator-name> -T <toolset-spec> -A <platform-name><path-to-source>`
- 通过指定`-G"MinGW Makefiles"`来指定**cmake使用gcc**

```cmake
cmake -B build -G "MinGW Makefiles"
cmake --build build
运行
cd build 
hello.exe
```

### 7. Linux下使用CMake构建项目

- Linux下安装cmake
    - sudo apt-get install cmake

- 源码安装
    - 推荐源码安装,感受一下C++的编译速度
    - sudo apt-get install build-essential
    - sudo wget https://cmake.org/files/v3.28/cmake-3.28.0.tar.gz
    - tar -zxvf cmake-3.28.0.tar.gz
    - cd cmake-3.28.0
    - ./configure
    - sudo make
    - sudo make install
    - cmake --version 检查是否安装成功

## 第二章 CMake Language概述

- CMake项目是基于CMakeLists.txt构建的,在CMakeLists.txt中(或是*.cmake)我们用到的就是CMake Language
- CMake Language的语法非常像一些**命令式编程语言**
- 执行从源树(CMakeLists.txt)的根文件文件开始

### 2.1 cmake命令行工具

cmake命令行工具是由五个可执行文件构成

1. **cmake**
2. **ctest**
3. **cpack**
4. **cmake-gui**
5. **ccmake**

如何不通过CMakeLists.txt运行CMake,`cmake -P *.cmake`,以上用法很少在项目中用到,但适合学习CMake语法

### 2.2 变量操作 set、list

- CMake中的变量分为两种
    - CMake提供
    - 自定义
- CMake变量的命名**区分大小写**
- CMake中的变量在存储时都是**字符串**
- CMake获取变量: **$(变量名}**
- 变量的基础操作是 set()与unset(),但你也可以用list或是string操作变量

### 2.3 set方法

- `set(<variable> <value>... [PARENT SCOPE])`
- set可以给一个变量设置多个值
- 变量内部存储时使用”;”分割,但显示时只进行连接处理

### 2.4 list方法

- `list(APPEND <list> [<element>...])`列表添加元素
- `list(REMOVE ITEM <list> <value> [value..])`列表删除元素
- `list(LENGTH <list> <output variable>)`获取列表元素个数
- `list(FIND <list> <value> <out-var>)`在列表中查找元素返回索引
- `list(INSERT <list> <index> [<element>...])`在index位置插入
- `list(REVERSE <list>)`反转list
- `list(SORT <list> [..])` 排序list

```cmake
cmake_minimum_required(VERSION 3.20)
# 打印信息
message("Hello, CMake!")
# 多行
message("This is a message
on multiple lines.")
message([[This is 
message]])

#  获取cmake的信息 ${}
message("CMAKE_VERSION: ${CMAKE_VERSION}")

# set()方法
set(VAR1 "value1")
set("my var" "value2")  # 注意空格,输出时要加转义符\
message("VAR1: ${VAR1}")    
message("my var: ${my\ var}")
# 设置多个值
set(LISTVALUE a1 a2)
message("LISTVALUE: ${LISTVALUE}")  # 输出 a1;a2
set(LISTVALUE a1;a2)
message("LISTVALUE: ${LISTVALUE}")  # 输出 a1;a2

# 系统变量
message($ENV{PATH})
set(ENV{CXX} G++)
message($ENV{CXX})

# unset()方法
unset(ENV{CXX})
# message($ENV{CXX}) 保错

# list方法
list(APPEND mylist a b c)
message("mylist: ${mylist}")    # a;b;c
list(LENGTH mylist len)
message("mylist length: ${len}")
list(GET mylist 1 elem)
message("mylist[1]: ${elem}")
list(REMOVE_AT mylist 1)
message("mylist after remove: ${mylist}")
list(INSERT mylist 1 x y z)
message("mylist after insert: ${mylist}")
list(FIND mylist x index)
message("index of x in mylist: ${index}")
list(SUBLIST mylist 1 3 sublist)
message("sublist: ${sublist}")
list(REVERSE sublist)
message("reverse sublist: ${sublist}")
list(SORT sublist)
message("sorted sublist: ${sublist}")
list(POP_FRONT sublist elem)
message("pop front elem: ${elem}, sublist: ${sublist}")
list(POP_BACK sublist elem)
message("pop back elem: ${elem}, sublist: ${sublist}")
list(JOIN sublist ";" str)
message("joined sublist: ${str}")
```

## 第三章 流程控制

### if 条件流程控制

- 条件if语句

```cmake
if(<condition>)<commands>
elseif(<condition>)<commands>
else()<commands>
endif()
```

### loop 循环流程控制

- For循环

```cmake
foreach(<loop_var> RANGE <max>)
<commands>
endforeach()

foreach(<loop_var> RANGE <min> <max> [<step>])
foreach(<loop variable> IN [LISTS <lists>] [ITEMS <items>])
```

- While循环

```cmake
while(<condition>)
<commands>
endwhile()
```

- break和continue使用类似其他语言

### 示例代码

```cmake
cmake_minimum_required(VERSION 3.20.0)
set (VARBOOL TRUE)

if(VARBOOL)
    message(STATUS "VARBOOL is true")
else()
    message(STATUS "VARBOOL is false")
endif()

if(NOT VARBOOL)
    message(STATUS "VARBOOL is true")
else()
    message(STATUS "VARBOOL is false")
endif()

if(NOT VARBOOL OR VARBOOL)
    message(TRUE)
else()
    message(FALSE)
endif()

if(NOT VARBOOL AND VARBOOL)
    message(TRUE)
else()
    message(FALSE)
endif()

if(1 LESS 2)
    message("1 is less than 2")
else()
endif()
    
if("ok" LESS 233)
    message("ok is less than 233")
else()
endif("")

if("2" EQUAL 2)
    message("'2' is equal to 2")
else()
endif()

cmake_minimum_required(VERSION 3.20)

foreach(i RANGE 1 5)
    message(${i})
endforeach()

set(MY_LIST 1 2 3 4 5)
foreach(i ${MY_LIST})
    message(${i})
endforeach()

foreach(VAR IN LISTS MY_LIST ITEMS 1 2 3 4 5)
    message(${VAR})
    
endforeach()

# zip
set(L1 one two three four)
set(L2 1 2 3 4 5)
foreach(num IN ZIP_LISTS L1 L2)
message("word = ${num_0}, num = ${num_1}")
endforeach()
```

## 第四章 函数

**定义函数的语法**

```cmake
function(<name> [<argument>...])
<commands>endfunction()
```

函数参数**第一个参数是函数名称**

```cmake
cmake_minimum_required(VERSION 3.20.0)

function(MyFunc FirstArg)
    message("MyFunc Name: ${CMAKE_CURRENT_FUNCTION}")
    message("FirstArg: ${FirstArg}")
    set(FirstArg "New value") 
    message("FirstArg again: ${FirstArg}")
    message("ARGV0: ${ARGV0}")  # 注意: CMake 默认的 ARGV0, ARGV1 等在这里可能没有定义
    message("ARGV1: ${ARGV1}")
    message("ARGV2: ${ARGV2}")
endfunction()

set(FirstArg "first value")
MyFunc(${FirstArg} "value")
message("FirstArg: ${FirstArg}")    # 并没有修改,作用域问题
```

## 第五章 Scope作用域

CMake 有两种作用域

1. Function scope 函数作用域
2. Directory scope 当从`add_subdirectory()`命令执行嵌套目录中的CMakeLists.txt列表文件时,注意父CMakeLists.txt其中的变量可以被子CMakeLists.txt使用

```cmake
cmake_minimum_required(VERSION 3.20.0)

# 定义输出函数
function(outFunc)
    message("-> out: ${Var}")
    set(Var 2)
    InFunc()
    message("<- out: ${Var}")
endfunction()

# 定义输入函数
function(InFunc)
    message("-> In: ${Var}")
    set(Var 3)
    message("<- In: ${Var}")
endfunction()

# 设置全局变量
set(Var 1)
message("-> Global: ${Var}")

# 调用输出函数
outFunc()

# 输出全局变量的值
message("<- Global: ${Var}")
```

## 第六章 宏

**CMake中的宏**,`宏是直接把代码替换到调用处`

```cmake
macro(<name> [<argument>...])
<commands>
endmacro()
```

注意:尽量不要写宏,只要会读就好

```cmake
cmake_minimum_required(VERSION 3.15)

macro(Test MyVar)
set(${MyVar} "new Value")   # 创建了一个新的变量并赋值
message("args ${MyVar}")   
endmacro()

set (MyVar "old Value")
message("before macro ${MyVar}")
Test(MyVar)
message("after macro ${MyVar}")
```

## 第七章 CMake构建项目的四种方式

#### 第一节: 直接写入源码路径的方式

- `add_executable`中直接写入相对路径,在源码中引入头文件时需要写**相对路径**

```cmake
cmake_minimum_required(VERSION 3.20)

project(animal CXX)

add_executable(Animal main.cpp animal/dog.cpp)
```

#### 第二节: 调用子目录cmake脚本的方法

- include方法可以引入子目录中的cmake后缀的配置文件
- 将配置加入add_executable中

```cmake
# animal.cmake
set(animal_sources animal/cat.cpp animal/dog.cpp) 
```

```cmake
# cmakelists.txt
cmake_minimum_required(VERSION 3.20)
project(animal CXX)
include(animal/animal.cmake)
add_executable(Animal main.cpp ${animal_sources})
```

#### 第三节:CMakeLists嵌套

- `target_include_directories` 头文件目录的声明
- `target_link_libraries` 连接库文件
- `add_subdirectory` 添加子目录
- `add_library` 生成库文件,默认是静态库 **STATIC library**

#### 第四节:Object Libraries

`add_library OBJECT`

- Object Library 是一个特殊的库类型,它将目标文件编译成一个库,但不会生成最终的链接文件。这意味着你可以在后续的 add_library() 或 add_executable() 命令中,将 Obiect Library 作为源文件进行链接,从而生成最终的可执行文件或库文件。
- 将target_include_directories移入到子CMakeLists中

## 第八章 动态库和静态库

### 1. 静态库和动态库

- 静态库
    在连接阶段,会将汇编生成的目标文件`.o`与引用到的库一起链接打包到可执行文件中。

    因此对应的链接方式称为静态连接。对函数库的连接是在编译时完成的!

- 动态库
    动态库不是在编译时被连接到目标代码中,而是运行时才被载入。

- **静态库对空间的浪费是巨大的!**

### 2. 命名

- 动态库的命名

    `lib<name>.so/dll`

- 静态库的命名

    `lib<name>.a/lib`

### 3. 命令

- `file()`常用于搜索源文件
- `add_library(animal STATIC ${SRC})`生成静态库
- `add_library(animal SHARED ${SRC})`生成动态库
- `${LIBRARY_OUTPUT-PATH}`导出目录

```cmake
cmake_minimum_required(VERSION 3.20)
project(Animal CXX)


# 把源文件加入到变量中
file(GLOB SRC ${PROJECT_SOURCE_DIR}/src/*.cpp)
include_directories(${PROJECT_SOURCE_DIR}/include)

# 生成静态库
# set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/a)
# add_library(Animal STATIC ${SRC})

# 生成动态库
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/so)
add_library(Animal SHARED ${SRC})
```

### 4. cmake使用动态库和静态库

静态库调用流程

1. 引入头文件
2. 连接静态库
3. 生成可执行二进制文件

```cmake
cmake_minimum_required(VERSION 3.20)
project(Animal CXX)

# 导入头文件
include_directories(${PROJECT_SOURCE_DIR}/include)
# 链接静态库
# link_directories(${PROJECT_SOURCE_DIR}/a)
# link_libraries(Animal)
# add_executable(app main.cpp)

# 链接动态库
link_directories(${PROJECT_SOURCE_DIR}/so)
add_executable(app main.cpp)
target_link_libraries(app PUBLIC Animal)
```

`生成完成后,可以把可执行文件放到动态库目录下,确保能找到`

## 第九章 cmake和源文件交互

- 在项目下新建`config.h.in`文件,里面用宏定义cmake变量`#define CMAKE_CXX_STANDARD ${CMAKE_CXX_STANDARD}   // 和源文件交互`

- animal文件夹中`cmakelists.txt` -> `add_library(AnimalLib cat.cpp dog.cpp)`

- 主项目cmakelists.txt文件
- 然后就可以在源文件中使用宏

```cmake
cmake_minimum_required(VERSION 3.20)
project(Animal CXX)

# 设置c++版本
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

configure_file(config.h.in config.h)
add_subdirectory(animal)

add_executable(Animal main.cpp)

target_link_libraries(Animal PUBLIC AnimalLib)

target_include_directories(Animal PUBLIC ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/animal)
```

## 第十章 cmake条件编译

- 通过不同传入参数编译不同的文件
    1. 用option定义变量
    2. 在子CMakeLists.txt中根据变量ON还是OFF来修改SRC(源文件)以及`target_compile_definitions`
    3. 修改源文件根据变量选择代码
    4. 执行命令时`-D<变量>`=ON/OFF来进行条件编译`cmake -B build -G "MinGW Makefiles" -DUSE_CATTWO=FALSE`就能正确地将 `USE_CATTWO` 设置为 `FALSE`。

### 说明 PUBLIC INTERFACE 和 PRIVATE的区别

- PUBLIC 本目标需要用,依赖这个目标的其他目标也需要
- INTERFACE 本目标不需要,依赖本目标的其他目标需要
- PRIVATE 本目标需要,依赖这个目标的其他目标不需要


原文地址:https://blog.csdn.net/x1343676/article/details/142716961

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!