# CMake 入门学习记录

这里是 CMake 学习的系统性记录，用于提高个人对代码底层构建的理解和部署。

> 所学课程来自 B 站 up主 爱编程的大丙 所录制的课程，共 21 集，总时长约 3.5 小时：
>
>
> <aside>
> 🔗
>
> [CMake 保姆级教程【C/C++】](https://www.bilibili.com/video/BV14s4y1g7Zj/?spm_id_from=333.788.videopod.episodes&vd_source=3ce53f73b64ce82f920fa1820727b9e2)
>
> </aside>
>
> 本课程是有配套博客资料的，由 up 主本人提供，共分为上、下两部分，链接如下：
>
> <aside>
> 🔗
>
> [[CMake 保姆级教程（上）](https://subingwen.cn/cmake/CMake-primer/)](CMake%20%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%95%99%E7%A8%8B%EF%BC%88%E4%B8%8A%EF%BC%89%20254669c3f4008007a46dc74f6a09da90.md)
>
> [[CMake 保姆级教程（下）](https://subingwen.cn/cmake/CMake-advanced/)（未整理）](CMake%20%E4%BF%9D%E5%A7%86%E7%BA%A7%E6%95%99%E7%A8%8B%EF%BC%88%E4%B8%8B%EF%BC%89%EF%BC%88%E6%9C%AA%E6%95%B4%E7%90%86%EF%BC%89%20254669c3f400808cad4ad9fadc8ab30b.md)
>
> </aside>
>

---

# 大纲

- [Linux 本地开发环境准备](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [CMake 概述](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [编写一个简单的 CMakeLists.txt 文件](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [CMake 中 set 的使用](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [搜索文件](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [指定头文件路径](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [通过 CMake 制作库文件](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [在程序中链接静态库](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [在程序中链接动态库](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [在 CMake 中打印日志信息](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
- [字符串操作](CMake%20%E5%85%A5%E9%97%A8%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%20250669c3f4008099a6d6c614a4ca6937.md)
-

---

# Linux 本地开发环境准备

使用 WSL 子系统在 Windows 11 本地部署 Ubuntu 22.04，使用 VS code 的 Remote - WSL 功能开发。

### **步骤 1：启用 WSL 功能**

1. **以管理员身份打开 PowerShell**：
    - 按 **`Win + X`**，选择 **Windows Terminal (Admin)** 或 **PowerShell (Admin)**。
2. **启用 WSL**：

    运行以下命令：

    ```powershell
    wsl --install
    ```

    这会自动安装 WSL 和默认的 Ubuntu 发行版（如果未安装，会提示下载）。

    安装成功后会提示：

    ```powershell
    正在安装: 虚拟机平台
    已安装 虚拟机平台。
    正在安装: 适用于 Linux 的 Windows 子系统
    已安装 适用于 Linux 的 Windows 子系统。
    正在安装: Ubuntu
    已安装 Ubuntu。
    请求的操作成功。直到重新启动系统前更改将不会生效。
    ```

3. **重启电脑**：

    确保 WSL 生效。


---

### **步骤 2：安装 Ubuntu 发行版**

1. **打开 Microsoft Store**：
    - 搜索 **Ubuntu**（推荐选择最新的 LTS 版本，如 **`Ubuntu 22.04 LTS`**）。
2. **点击安装**：
    - 安装完成后，从开始菜单打开 Ubuntu，会提示设置用户名和密码（Linux 环境专用，无需与 Windows 相同）。

在设置完用户名和密码后，Ubuntu 的命令提示符中会显示以下内容：

```bash
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.6.87.2-microsoft-standard-WSL2 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Tue Aug 19 11:21:39 CST 2025

  System load:  0.53                Processes:             62
  Usage of /:   0.1% of 1006.85GB   Users logged in:       0
  Memory usage: 3%                  IPv4 address for eth0: 172.20.77.158
  Swap usage:   0%

This message is shown once a day. To disable it please create the
/home/alvin-li/.hushlogin file.
alvin-li@66CCFF:~$
```

这样 Ubuntu 系统本身就已经安装完成了，我这里装的是 24.04.1 版本。

---

### **步骤 3：配置 WSL 和 Ubuntu**

1. **更新软件包列表**：

    在 Ubuntu 终端中运行：

    ```bash
    sudo apt update && sudo apt upgrade -y
    ```

2. **安装基础开发工具**：

    ```bash
    sudo apt install -y build-essential gcc g++ cmake make git
    ```

    - **`build-essential`**：包含 GCC、Make 等基础工具。
    - **`cmake`**：CMake 构建工具。

输入完这两行之后，Ubuntu 会自动安装**一堆**东西，静候即可，直到再次出现 **`alvin-li@66CCFF:~$` 。**

---

### **步骤 4：验证环境**

1. **检查 GCC 和 CMake**：

    ```bash
    gcc --version  # 查看 GCC 版本
    cmake --version  # 查看 CMake 版本
    ```

    分别返回 gcc 和 CMake 的版本号即为安装成功。


---

### **步骤 5：安装 VS Code 和必要扩展**

1. **下载并安装 VS Code**
    - 从官网下载安装：[Visual Studio Code](https://code.visualstudio.com/)。
2. **安装 Remote - WSL 扩展**
    - 打开 VS Code，点击左侧活动栏的 **扩展图标**（或按 **`Ctrl+Shift+X`**）。
    - 搜索 **Remote - WSL**（由 Microsoft 官方提供），点击安装。

---

### **步骤 6：连接 WSL 中的 Ubuntu**

1. **启动 WSL 终端**
    - 在 Windows 开始菜单中打开已安装的 **Ubuntu**（或通过 **`wsl`** 命令启动）。
2. **从 WSL 内启动 VS Code**
    - 在 Ubuntu 终端中，导航到你的项目目录（例如 **`cd ~/projects`**），然后运行：

        ```bash
        code .
        ```

    - 这会自动在 Windows 的 VS Code 中打开当前 WSL 目录，并建立远程连接。

---

### **步骤 7：配置开发环境**

**安装 C/C++ 扩展**

- 在 VS Code 的扩展市场中搜索 **C/C++**（Microsoft 官方扩展），安装时会提示 **"Install in WSL: Ubuntu"**，选择在 WSL 中安装。

至此，一个简单的 Linux 本地开发环境就配置完成了。

---

# CMake 概述

一款项目构建工具，可以实现大型项目的自动化管理。主体为 **CMakeLists.txt** 脚本，在里面部署一些指令，CMake 构建时会按文件中的命令调用对应的工具链部分，从而自己完成整个项目的编译。

**关键词**：**跨平台**、自动建立 toolchain 编译规则、多开发环境支持（VS、Qt、Clion）

**构建流程**：预编译（预处理）→ 编译 → 汇编 → 链接

若源代码比较少可以通过命令行去编译，但是一旦文件多了就不好处理，此时可以用 CMake 解决。CMake 不依赖于平台，比 Makefile 高级，可以对应不同平台创建对应的 Makefile。

学习 CMake 的成本较 Makefile 更低，效率更高。本次课程会学习一些常用 CMakeLists.txt 中的命令，可以解决 60% - 70% 的构建问题。

课程基于 Linux 开发环境讲解，所以在一切开始之前，需要先在本地部署带 CMake 和完整匹配的工具链的 Linux 虚拟机进行学习。（没用 Linux 云服务器是因为看了看各家都有点小贵，鉴于 CMake 的学习周期较短，直接干本地就完事了）

---

# 编写一个简单的 CMakeLists.txt 文件

首先准备了 5 个 .cpp 源文件和一个 .h 头文件，用于后面 CMake 构建：

```cpp
// head.h

#ifndef _HEAD_H
#define _HEAD_H
// 加法
int add(int a, int b);
// 减法
int subtract(int a, int b);
// 乘法
int multiply(int a, int b);
// 除法
double divide(int a, int b);
#endif
```

```cpp
// add.cpp

#include <stdio.h>
#include "head.h"

int add(int a, int b)
{
    return a + b;
}
```

```cpp
// sub.cpp

#include <stdio.h>
#include "head.h"

int subtract(int a, int b)
{
    return a-b;
}
```

```cpp
// mult.cpp

#include <stdio.h>
#include "head.h"

int multiply(int a, int b)
{
    return a*b;
}
```

```cpp
// div.cpp

#include <stdio.h>
#include "head.h"

double divide(int a, int b)
{
    return (double)a/b;
}
```

```cpp
// main.cpp

#include <stdio.h>
#include "head.h"

int main()
{
    int a = 20;
    int b = 12;
    printf("a = %d, b = %d\n", a, b);
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", subtract(a, b));
    printf("a * b = %d\n", multiply(a, b));
    printf("a / b = %f\n", divide(a, b));
    return 0;
}
```

这五个文件是在同一个目录下的，这里我放在了 **`~/CMake/demo`** 中。

用命令行方式编译上面的小程序其实也是可以实现的，只需要在命令行中输入：

```bash
g++ *.cpp -o app
```

即可在相同路径下编译出一个 app 可执行文件，这时候再输入：

```bash
# 运行可执行文件
./app
```

可以看到终端中成功运行了上述程序。

这样虽然可以正常执行编译流程，但是毕竟这只是一个一共只有 6 个文件的小程序，如果源文件和头文件的数量级来到数百上千，又或者在编译的时候需要引入额外的依赖文件或库，命令行的方式就不再实用了，而要用 CMake 构建。在这里为了演示原理，接下来用 CMake 处理上面的程序：

先在目录中创建一个文件，名为 **`CMakeLists.txt` （一个字都不能错，包括大小写）**，并写入：

```cpp
cmake_minimum_required(VERSION 3.25)
project(test)
add_executable(app add.c sub.cpp mult.cpp div.cpp main.cpp)
```

- **`cmake_minimum_required`**：指定使用的 CMake 的最低版本，可选
- **`project`**：定义工程名称和其他参数

    ```cpp
    # PROJECT 指令的语法是：

    project(<PROJECT-NAME> [<language-name>...])
    project(<PROJECT-NAME>
           [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
           [DESCRIPTION <project-description-string>]
           [HOMEPAGE_URL <url-string>]
           [LANGUAGES <language-name>...])
    ```

- **`add_executable`**：定义工程会生成一个可执行程序，指定 .exe 的名字

    ```cpp
    add_executable(可执行程序名 源文件名称)
    ```

    **注**：**`add_executable()`** 中后面的参数**一定是源文件（.cpp）**，不能有头文件（.h）。


CMakeLists.txt 准备好之后，在同目录的终端里运行 **`cmake`** 命令即可开始构建。

构建完成后发现同目录下多了一堆文件，其中存在一个 Makefile 文件，然后在终端中输入 **`make`** 命令，即可获得可执行程序 **`app`**，输入 **`./app`** 运行该程序，发现程序成功运行。

**注**：执行 **`cmake`** 命令时，命令后面跟的路径**一定是 CMakeLists.txt 所在的路径**。

---

# CMake 中 set 的使用

CMake 中可以使用 **`set`** 命令设置变量，变量类型为**字符串**，变量定义时名字不能重复。

```cpp
# SET 指令的语法是：
# [] 中的参数为可选项, 如不需要可以不写
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
```

**`VAR`**：变量名 / **`VALUE`**：变量值

**个人理解**：所谓的变量其实是多个源代码文件的一个集合，在有些项目中，某些源代码文件集合（好多个源代码文件）可能在编译的时候需要多次调用，每次都直接写在 executable 后面的括号中太繁琐了，故用 set 命令定义一个变量，变量中包含了某固定批量的源代码文件，这样在需要这批源文件时，直接将定义的变量名放进 executable 即可。

**部分使用方法**：

- 定义文件集合，即可将一堆文件集合组成的变量放入 **`add_executable()`** 方便调用

    ```cpp
    # 方式1: 各个源文件之间使用空格间隔
    et(SRC_LIST add.c div.c main.c mult.c sub.c)

    # 方式2: 各个源文件之间使用分号 ; 间隔
    set(SRC_LIST add.c;div.c;main.c;mult.c;sub.c)

    add_executable(app ${SRC_LIST})
    ```

    **注**：变量名一定要放入 **`${ }`** 中才能成功调用。

- 设置 C++ 标准

    ```cpp
    # 增加-std=c++11
    set(CMAKE_CXX_STANDARD 11)
    # 增加-std=c++14
    set(CMAKE_CXX_STANDARD 14)
    # 增加-std=c++17
    set(CMAKE_CXX_STANDARD 17)
    ```

    **注**：也可另选在执行 **`cmake`** 命令的时候指定标准。

- 使用 **`EXECUTABLE_OUTPUT_PATH`** 宏指定最终生成的可执行文件生成文件

    ```cpp
    set(HOME /home/robin/Linux/Sort)            # 定义一个变量用于存储一个绝对路径
    set(EXECUTABLE_OUTPUT_PATH ${HOME}/bin)     # 将拼好的路径值设给EXECUTABLE_OUTPUT_PATH

    # 如果这个路径中的子目录不存在，会自动生成，无需自己手动创建
    ```

    **注**：变量存储路径时，建议使用**绝对路径**。


---

# 搜索文件

使用 **`set`** 命令声明变量后虽然 **`add_executable`** 中的内容变得简洁了，但其实还没有解决本质问题，那就是需要手动输入每个需要参与编译的文件。这时就需要一种命令，可以帮我们搜集所有需要的的文件，在 CMake 中，可以使用 **`aux_source_directory`** 命令或者 **`file`** 命令实现。

- **`aux_source_directory`** 命令可以查找某个路径下的所有源文件（.cpp），命令格式为：

    ```cpp
    # 指令语法：
    aux_source_directory(< dir > < variable >)
    ```

    **`dir`**：要搜索的目录  / **`variable`**：将从 dir 目录下搜索到的源文件列表存储到该变量中

    **用例**：搜索当前目录的 src 目录下所有的源文件（.cpp），并存储到变量中。

    ```cpp
    cmake_minimum_required(VERSION 3.0)
    project(CALC)

    # 搜索 src 目录下的源文件
    aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/src SRC_LIST)
    add_executable(app  ${SRC_LIST})
    ```

- **`file`** 命令也可以做到上面的事情（当然，除了搜索以外还可以做其他事情），命令格式为：

    ```cpp

    # 指令语法：
    file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
    ```

    **`GLOB`**: 将指定目录下 搜索到的 满足条件的 所有文件名 生成一个列表，并将其存储到变量中
    **`GLOB_RECURSE`**：功能同上，但是会递归搜索指定目录（目录中的所有子文件夹也搜）

    **用例**：搜索当前目录的 src 目录下所有的源文件（.cpp），并存储到变量中。

    ```bash
    file(GLOB MAIN_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
    file(GLOB MAIN_HEAD ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h)
    ```


**注**：**`CMAKE_CURRENT_SOURCE_DIR`** 宏表示当前访问的 CMakeLists.txt 文件所在的路径。

---

# 指定头文件路径

当项目体积增大，文件变的多而繁杂的时候，一般都会将源文件和头文件放进不同的路径中管理。这就会出现某些头文件的路径不与 **`CMakeLists.txt`** 所在路径一致。**`CMakeLists.txt`** 中没有描述这些头文件的声明，此时 CMake 就会自然而然的以为构建需要用到的头文件（.h）都处在和 **`CMakeLists.txt`** 相同的路径中，从而在后面执行 **`make`** 命令的时候导致 make 找不到需要的头文件从而报错。这时候就需要有一个 CMake 命令去指定头文件在哪里。在 CMake 中，我们用 **`include_directories()`** 命令来定义头文件的路径，从而帮助项目正确构建。

- **`include_directories()`** 命令可以显示声明头文件所在的路径，命令格式为：

    ```cpp
    # 指令语法：
    include_directories(< headpath >)
    ```

    **`headpath`**：头文件所在路径（一定是**路径**！不能是头文件名或其集合变量！）


**用例 1**：若头文件都在 **`CMakeLists.txt`** 所在路径中的一个叫 include 的文件夹内

```cpp
# 指定头文件的位置
include_directories(${PROJECT_SOURCE_DIR}/include)
```

**用例 2**：和 **`set`** 命令搭配使用显示指定头文件路径

```cpp
# 定义包含目录的变量 INCLUDE_DIRS
set(INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/include)

# 使用变量
include_directories(${INCLUDE_DIRS})
```

---

# 通过 CMake 制作库文件

项目代码写完，一般会将项目 release 到公共平台上。有时我们并不想让别人知道我代码中的实现逻辑是什么，也不想让源码公开，而项目运行却偏偏需要这些源码。这时为了让别人仅使用功能、不获取源码，可以将源码打包成库的方式发放给别人。这些库的本质上其实还是机器码、符号表等在主程序执行时会调用的内容，但跟 **`.cpp`** 不同的是它是以二进制的形式保存的，只有机器能看懂。

库的命名方式一般为 **`lib + 库名 + 后缀`** 的形式，分为两种类型，一种是静态库，一种是动态库。静态库在 Windows 中以 **`.lib`** 结尾，在 Linux 中以 **`.a`** 结尾，且其不可执行；动态库在 Windows 中以 **`.dll`** 结尾，在 Linux 中以 **`.so`** 结尾，且在机器的视角中，动态库为可执行文件（不完整，无程序入口点，只有部分可执行代码）。在 CMake 中，可以使用 **`add_library()`** 命令生成库文件。

- 如果要制作静态库，需要使用的命令如下：

    ```cpp
    # 制作静态库
    add_library(库名称 STATIC 源文件1 源文件2 ...)
    ```

- 如果要制作动态库，需要使用的命令如下：

    ```cpp
    # 制作动态库
    add_library(库名称 SHARED 源文件1 源文件2 ...)
    ```


**注**：在 Linux 中，静态库名字分为三部分：**lib+库名字+.a**，此处只需要指定出**库的名字**就可以了，另外两部分在生成该文件的时候会自动填充。

**用例 1**：和 **`file`** 命令搭配生成不同的库

```cpp
# 收集生成库用到的文件
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")

# 生成静态库
add_library(calc_st SHARED ${SRC_LIST})

# 生成动态库
add_library(calc_sh SHARED ${SRC_LIST})
```

**用例 2**：搭配 **`set`** 命令和 **`LIBRARY_OUTPUT_PATH`** 宏指定库的输出路径

```cpp
# 收集生成库用到的文件
file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")

# 用 set 命令指定静态库、动态库文件输出路径
set(LIBRARY_OUTPUT_PATH /home/alvin-li/CMake/demo_v1)

# 生成静态库到上述路径中
add_library(calc_st SHARED ${SRC_LIST})

# 生成动态库到上述路径中
add_library(calc_sh SHARED ${SRC_LIST})
```

---

# 在程序中链接静态库

获得的库文件是为了将来在可执行程序中调用的。先来说一下静态库的调用方式，若一个可执行程序在运行时需要调用静态库时，静态库就会在可执行程序**开始运行**的时候被加载到内存中。在 CMake 中，用如下命令可以调用静态库：

```cpp
# 调用静态库，括号中依次写出调用库的名字即可
link_libraries(<static lib> <static lib>...)
```

**用例 1**：假设选择我们有静态库文件 **`libcalc.a`**：

```cpp
# 在构建时链接静态库
link_libraries(calc)
```

**注**：括号内的库名可以是掐头去尾的 **`calc`**，也可以是完整名称 **`libcalc.a`**。

**用例 2**：若需要调用的库不为系统自带的库（自己制作或第三方提供的库），则需要显示声明这些库 的路径在哪，在 CMake 中，使用 **`link_directories()`** 命令即可：

```cpp
# 声明静态库路径，假设该库在 CMakeLists.txt 所在路径中的 lib 文件夹内
link_directories(${PROJECT_SOURCE_DIR}/lib)

# 链接静态库
link_libraries(calc)
```

**注**：**`link_directories()`** 命令**一定要写在 `add_executable()` 命令之前**；如有多个库待链接，也可以使用 **`file`** 命令定义库集合变量再链接。

---

# 在程序中链接动态库

动态库和静态库最大的调用区别就是链接到可执行程序文件的时机，静态库是在可执行程序**编译时**就被链接到可执行程序中的，在程序启动时就会被同步映射到虚拟内存当中；而动态库只有当可执行程序**运行时**调用，才会被链接、加载到虚拟内存空间。而且动态库可以同时被多个进程共享调用，静态库则会被复制多份在不同进程中各自工作。在 CMake 中，用以下方式链接动态库：

```cpp
# 链接动态库，这个命令要放在目标创建之后（add_executable() 之后）调用
target_link_libraries(
    <target>
    <PRIVATE|PUBLIC|INTERFACE> <item>...
    <PRIVATE|PUBLIC|INTERFACE> <item>...)
```

**`target`**：动态库要链接到的文件的名字：
该文件可能是一个源文件（**`.cpp`**），
该文件可能是一个动态库（**`.so / .dll`**），
该文件可能是一个可执行文件（**`.exe`**）

**`PRIVATE | PUBLIC | INTERFACE`**：动态库的访问权限，默认（不写）为 **`PUBLIC`**

**访问权限详解**

动态库的链接具有传递性，如果动态库 A 链接了动态库 B、C，动态库 D 链接了动态库 A，此时动态库 D 相当于也链接了动态库 B、C，并可以使用动态库 B、C 中定义的方法。若动态库之间没有依赖关系，无需做任何设置，使用默认的 **`PUBLIC`** 即可。

如果对保密性有要求，不希望库中的函数被多次传递或定位，可以在链接动态库时使用访问权限关键字限制传递权限，具体解释如下：

- **`PRIVATE` （保密性最高）**：

    在 private 后面的库仅被链接到前面的 target 中，并且止步于此，后续无法传递给其他依赖 target 的文件，后续文件无法使用该库中的内容。


- **`PUBLIC` （无保密性）**：

    在 public 后面的库会被链接到前面的 target 中，并且里面的内容也会被传递给所有依赖者。


- **`INTERFACE` （保密性中等）**：

    在 interface 后面的库不会被链接到 target 中，但后续传递者需要链接该库，一般为头文件库。


**用例 1**：将动态库 **`libpthread.so`** 链接到可执行程序 **`app`** 上：

```cpp
# 添加并指定最终生成的可执行程序名
add_executable(app ${SRC})

# 指定可执行程序要链接的动态库名字，这里默认权限为 PUBLIC
target_link_libraries(app pthread)
```

**注**：库名和静态库一样，可以是掐头去尾的 **`pthread`**，也可以是完整名称 **`libpthread.so`** 。

**用例 2**：将第三方动态库 **`libcalc.so`** 链接到可执行程序 **`app`** 上，需要先**显示声明库路径**：

```cpp
# 指定要链接的动态库的路径，路径声明要放在 add_executable() 之前
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib_sh)

# 添加并生成一个可执行程序
add_executable(app ${SRC})

# 指定要链接的第三方动态库
target_link_libraries(app calc)
```

---

# 在 CMake 中打印日志信息

与其他的编程语言一样，在 CMake 中也有运行时往控制台输出消息内容的命令。在这里，运行时是指执行 **`cmake`** 命令后，CMake 开始构建时所在控制台输出的消息。CMake 输出消息的命令如下：

```cpp
# 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 区域显示所有消息。

> **疑问**：**`stdout`** 和 **`stderr`** 的区别和关系是什么？
>
>
> <aside>
> 💡
>
> 1. **标准输出 (`stdout`)**
>     - **目的**：用于输出程序的**常规、预期的结果**。
>     - **内容**：程序正常运行产生的数据、请求的信息、最终的计算结果等。
> 2. **标准错误 (`stderr`)**
>     - **目的**：用于输出**错误消息、警告、诊断信息、状态日志**。
>     - **内容**：错误信息（如“文件未找到”）、警告、调试信息、进度更新等。
> </aside>
>

**用例**：

```cpp
# 普通消息打印到控制台
message("xxxxxxxxxxxxxxxxxxx")

# 状态消息打印到控制台
message(STATUS "11111xxxxxxxxxxxxxxxxxxx")

# 致命错误消息打印到控制台
message(FATAL_ERROR "22222xxxxxxxxxxxxxxxxxxx")

# 状态消息打印到控制台，不执行
message(STATUS "33333xxxxxxxxxxxxxxxxxxx")
```

以上部分输出内容：

```bash
# 普通消息
xxxxxxxxxxxxxxxxxxx

# 状态消息 STATUS
-- 11111xxxxxxxxxxxxxxxxxxx

# 致命错误消息 FATAL_ERROR
CMake Error at CMakeLists.txt:61 (message):
  22222xxxxxxxxxxxxxxxxxxx

# 后续构建不执行，看不到 33333xxx...
```

---

# 字符串操作
