---
title: "C语言项目构建（Makefile与CMake）"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/c-project-build-makefile-cmake/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/02-编程语言与运行时/C语言项目构建（Makefile与CMake）.md"
content_hash: 2a805b9ae4c7ea2c5ac68692f3cf15e42a550d154c5ed1d7c4b27db000ce4968
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# C语言项目构建（Makefile与CMake）
## 静态库与共享(动态链接)库

### 静态库（Static Library）

- **定义**：它静态库在编译时被嵌入到使用它的可执行文件中，这意味着生成的可执行文件包含了库的所有实现，因此在运行时不需要依赖外部库。文件扩展名通常是 `.a`（在Unix和Linux上）或 `.lib`（在Windows上）。
**编译过程**：
- 在创建一个 `STATIC` 库时，CMake 会将所有对象文件（`.o` 文件）打包成一个静态库文件，但不会生成可执行代码。
- 链接器会将这些对象文件复制到可执行文件中，并将符号解析到具体的实现，生成一个独立的可执行文件。
**链接和加载**：
- 静态库的所有代码都被嵌入到可执行文件中，程序在运行时不依赖外部的静态库文件。
**优点**：
- **自包含**：由于库代码直接包含在可执行文件中，运行时不需要额外的库文件。这使得分发程序变得简单。
- **加载速度快**：因为所有代码已经包含在可执行文件中，不需要在运行时加载外部库。
**缺点**：
- **体积大**：每个可执行文件都包含库代码，会导致较大的文件尺寸。
- **更新复杂**：如果库有更新，必须重新编译所有使用该库的程序。

### 共享库（Shared Library）

**定义**：共享库是在运行时动态加载的库。它们的文件扩展名通常是 `.so` （Shared Object，Unix和Linux上）或 `.dll`（Dynamic Link Library，Windows上）。

**编译过程**：
	- 在创建一个 `SHARED` 库时，CMake 会生成一个动态库文件，其中包含所有定义的函数和全局变量的实现，以及一个导出符号表。
	- 导出符号表列出了所有可以被外部程序访问的符号（函数和变量），使得链接器能够在运行时解析和调用这些符号。
**链接和加载**：
	- 在链接阶段，链接器只会记录一个引用，指向共享库的路径，而不是将库的实现复制到可执行文件中。
	- 在程序运行时，操作系统会加载共享库并解析符号，动态地将函数调用连接到共享库中的实际实现。
**优点**：
    - **节省内存**：多个运行的程序可以共享一个共享库的实例，减少整体内存使用。
    - **更新简便**：可以单独更新共享库而不需要重新编译依赖它的程序。
**缺点**：
    - **依赖问题**：运行时需要确保共享库存在并且版本匹配。缺失或版本不兼容的库会导致程序无法运行。
    - **加载开销**：运行时需要加载库文件，可能会影响启动速度。


### **OBJECT Library（对象库）**

**定义和原理**：
- `OBJECT` 库是一种特殊类型的库，它并不会生成一个真正的库文件（如 `.a` 或 `.so`），而只是将源文件编译为对象文件（`.o` 或 `.obj`）。
- 这些对象文件可以被其他目标（如 `SHARED` 或 `STATIC` 库，或者可执行文件）直接使用。
**编译过程**：
- CMake 会将所有源文件编译为对象文件，但不会打包成一个独立的库文件。对象文件保存在构建目录中。
- 这些对象文件可以在同一 CMake 项目中被多个目标共享，从而避免重复编译。
**链接和加载**：
- 在构建最终的 `SHARED` 或 `STATIC` 库，或者可执行文件时，`OBJECT` 库中的对象文件会被直接包含和链接。
- 优点：可以共享编译的对象文件，减少编译时间，特别是在大型项目中。
- 缺点：`OBJECT` 库不能独立使用或分发，必须与其他目标一起使用。

## 交叉编译
交叉编译是指在一个平台上生成可以在另一个平台上运行的二进制文件的过程。典型的例子是：
- 在 x86 架构的主机上编译 ARM 架构的目标代码。
- 在 Windows 系统上编译 Linux 可执行文件。

## 编译器：将源代码编译为机器码

### GCC系列
**GCC（GNU Compiler Collection）**：GCC 是最早支持多种语言的编译器之一，它是一个由 **GNU 项目** 开发的编译器集合，支持包括 C、C++、Objective-C、Fortran、Ada 和 Go 等。GCC 是自由开源软件，最初是为了实现 GNU 操作系统的 C 编译器，但后来扩展为支持多种编程语言的编译器集。

> 最早由 **Richard Stallman** 于 1987 年发布，它是 GNU 项目的一部分，目标是创建一个自由的、完全开放源代码的操作系统（GNU 操作系统


 **G++** 是 **GCC（GNU Compiler Collection）** 中的 C++ 编译器部分，它是 GCC 工具链中的一员，专门用于处理 C++ 代码。
- **C++ 支持**：G++ 是 GCC 编译器家族中的 C++ 编译器，支持从 C++98 到最新的 C++ 标准（如 C++20、C++23）。
- **与 GCC 紧密集成**：G++ 与 GCC 的其他部分（如 GDB、ld、make 等）紧密集成，特别是在 Unix 和 Linux 系统上，它是默认的 C++ 编译器。它能够生成调试符号、优化代码，并支持跨平台编译。
- **命令行使用**：`g++` 和 `gcc` 使用的命令行参数非常相似，区别在于，`g++` 默认会链接 C++ 的标准库（如 `libstdc++`），而 `gcc` 则主要用于 C 语言，默认不链接 C++ 库。

### **Clang/LLVM**
Clang 是一个基于 LLVM 的 C/C++ 编译器，起初由苹果公司开发。Clang 拥有模块化架构，提供了比 GCC 更快的编译速度和更好的错误信息。

**Clang++** 是 **Clang** 编译器项目中用于编译 C++ 代码的部分，Clang 本身是 **LLVM** 项目的一部分，提供了一个现代的、模块化的 C/C++ 编译器。

> Clang++ 是 LLVM 项目的前端，负责解析 C++ 代码并生成 LLVM IR（中间表示），LLVM 的后端负责将 IR 生成目标机器码。

> LLVM原本代表“Low Level Virtual Machine”，即低级虚拟机。这个项目最初是为了提供一个现代的编译技术框架，使得编译时、链接时、运行时以及“空闲时”（即程序未运行时）的编译器优化和代码生成能够更加简单和模块化。
> 随着时间的发展，LLVM已经扩展成一个广泛的项目集，包括多个子项目，例如：
> 	**LLVM Core**：提供编译器的基础架构，支持多种编程语言的编译和优化。
> 	**Clang**：一个C/C++/Objective-C编译器，基于LLVM架构设计，以其编译速度快和优良的错误和警告消息著称。
> 	**LLD**：LLVM的链接器，用于将编译后的代码模块链接成可执行文件。
> 	**libc++**：C++标准库的实现，用于支持C++的各种标准功能。

## **链接器：将编译后的目标文件链接成可执行文件或库**

链接器是编译后阶段的关键工具，它将多个目标文件或库文件链接为一个可执行文件或动态库。

- **GNU ld**：GNU 链接器是 GCC 工具链的一部分，用于链接目标文件。
- **Gold linker**：是 GNU 项目的一个替代链工具，它比传统的 `ld` 更快，尤其适用于大型项目。
- **LLD**：LLVM 项目中的链接器，与 Clang 一起使用，可以替代 GNU ld。

### 其他
**Intel C++ Compiler（icc）**：用于英特尔硬件优化的编译器，常用于高性能计算。


## 构建系统：管理编译和链接的自动化流程
- **Makefile 和 GNU Make**
    - **Make** 是最早用于自动化构建的工具之一。GNU Make 是 GNU 项目开发的开源实现，它使用 **Makefile** 来描述如何编译和链接程序。
    - **Makefile** 指定了源文件、目标文件及其依赖关系，`make` 工具通过 `Makefile` 的规则执行增量编译。
- **Autotools**
    - **Autotools** 是 GNU 的一套构建系统工具，包括 `autoconf`、`automake` 和 `libtool`。它主要用于生成便携式的 `Makefile`，从而使程序可以在不同平台上进行构建。
    - `autoconf` 用于检测系统环境，`automake` 帮助生成标准化的 Makefile，`libtool` 管理共享库的构建。
- **CMake**
    - **CMake** 是一种现代化的跨平台构建系统，它生成平台特定的构建文件（如 Makefile、Visual Studio 工程等）。CMake 在跨平台开发中非常流行，特别是在 C++ 项目中。
    - CMake 通过 `CMakeLists.txt` 来管理项目的构建配置，它支持高度灵活的构建流程，适用于各种编译器和构建环境。
- **Bazel**
    - **Bazel** 是由 Google 开发的一个高效的构建工具，旨在管理大规模的代码库。Bazel 提供了高度可扩展性和并行化编译，特别适合 C++、Java 和 Python 项目。
- **Ninja**
    - **Ninja** 是一个轻量级的构建系统，旨在加速编译过程，特别适合大规模的并行编译。它常常与 CMake 一起使用，CMake 可以生成 Ninja 文件，然后通过 Ninja 执行编译。


## make与Makefile

## Makefile
Makefile 是用于自动化构建过程的文件，广泛用于 Unix 和类 Unix 操作系统中。它通过定义一系列规则来描述如何从源文件生成目标文件。Makefile 主要由目标、依赖和命令三部分组成。
- **目标（Target）**：要生成的文件或结果。
- **依赖（Dependencies）**：生成目标所需要的文件或其他目标。
- **命令（Commands）**：生成目标的步骤。通常是编译、链接或其他构建操作。

Makefile 的工作流程如下：
- `make` 工具读取 Makefile。
- 根据目标和依赖关系确定需要更新的目标。
- 执行相应的命令来更新目标。

Makefile文件示例：

```makefile
## 定义变量
CC = gcc
CFLAGS = -Wall -g

## 定义目标
TARGET = myprogram

## 定义规则
$(TARGET): main.o utils.o
	$(CC) $(CFLAGS) -o $(TARGET) main.o utils.o

main.o: main.c
	$(CC) $(CFLAGS) -c main.c

utils.o: utils.c
	$(CC) $(CFLAGS) -c utils.c

## 清理规则
clean:
	rm -f $(TARGET) *.o
```

## Make 的各种命令及其作用
`make` 是一个构建自动化工具，用于管理项目的编译和构建过程。它通过读取 Makefile 文件中的规则来确定哪些文件需要重新生成，并执行相应的命令。

1. `make`
- **作用**：执行 Makefile 中的默认目标（通常是第一个目标）。
- **示例**：直接在命令行输入 `make`。

2. `make <target>`
- **作用**：执行指定的目标及其依赖项。
- **示例**：`make clean` 执行 Makefile 中定义的 `clean` 目标。

3. `make all`
- **作用**：通常用于构建所有目标文件。这是一个约定俗成的目标名称。
- **示例**：`make all` 会构建所有定义的目标。

4. `make clean`
- **作用**：清理构建生成的文件。该目标通常用于删除编译过程中生成的中间文件和可执行文件。
- **示例**：`make clean` 会执行 Makefile 中 `clean` 目标的命令，通常是删除编译生成的文件。

5. `make install`
- **作用**：将构建生成的文件安装到系统的指定目录中。这个目标通常用于将可执行文件、库文件、头文件等复制到系统的安装目录（例如 `/usr/local/bin`、`/usr/local/lib` 等）。
- **示例**：`make install` 会执行 Makefile 中 `install` 目标的命令，将文件复制到指定的安装目录。

6. `make uninstall`
- **作用**：从系统中删除安装的文件。这个目标通常与 `install` 目标对应，用于清理安装的文件。
- **示例**：`make uninstall` 会执行 Makefile 中 `uninstall` 目标的命令，删除安装的文件。

7. `make <target> -jN`
- **作用**：并行执行目标及其依赖项中的命令。`N` 指定并行执行的最大任务数。
- **示例**：`make -j4` 会同时执行最多 4 个任务，以加快构建速度。

假设有一个简单的项目，其 Makefile 如下：
```makefile
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
INCLUDEDIR = $(PREFIX)/include
LIBDIR = $(PREFIX)/lib

TARGET = myprogram
SRC = main.c
OBJ = main.o

CC = gcc
CFLAGS = -Wall -g

all: $(TARGET)

$(TARGET): $(OBJ)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJ)

%.o: %.c
	$(CC) $(CFLAGS) -c $<

install: $(TARGET)
	mkdir -p $(BINDIR) $(INCLUDEDIR) $(LIBDIR)
	cp $(TARGET) $(BINDIR)
	cp myprogram.h $(INCLUDEDIR)
	cp libmyprogram.a $(LIBDIR)

clean:
	rm -f $(TARGET) $(OBJ)

uninstall:
	rm -f $(BINDIR)/$(TARGET)
	rm -f $(INCLUDEDIR)/myprogram.h
	rm -f $(LIBDIR)/libmyprogram.a
```

在这个示例中，`install` 目标会执行以下操作：
1. 创建必要的目录（如果它们不存在）。
2. 将生成的可执行文件 `myprogram` 复制到 `/usr/local/bin`。
3. 将头文件 `myprogram.h` 复制到 `/usr/local/include`。
4. 将静态库 `libmyprogram.a` 复制到 `/usr/local/lib`。


## Autotools
## **1. 编写配置文件**

 **`configure.ac`**：定义项目的基础信息、编译依赖和系统检查规则。
 **`Makefile.am`**：`Makefile` 的模板文件，定义构建目标（如可执行文件、库、源文件）及构建规则。

```bash
## configure.ac
AC_INIT([MyProject], [1.0], [me@example.com])
AM_INIT_AUTOMAKE([foreign])
AC_PROG_CC  # 检查是否存在 C 编译器
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
```

```ini
## Makefile.am
bin_PROGRAMS = myprogram  # 定义目标可执行文件
myprogram_SOURCES = main.c  # 定义源文件
```

## **2. 生成宏定义文件：`aclocal`**

**工具：**
- `aclocal`：从系统和第三方库中收集宏定义，生成 **`aclocal.m4`**。
**生成的文件：**
- **`aclocal.m4`**：包含项目使用的宏定义，用于在配置过程中调用。
**作用：**
- 收集和准备项目所需的宏定义，确保系统依赖项能被正确识别

## **3. 生成配置脚本configure：`autoconf`**

**工具：**
- `autoconf`：根据 `configure.ac` 生成 **`configure`** 脚本。
**生成的文件：**
- **`configure`**：会嵌入所有宏和系统检查逻辑，确保项目能适应不同系统, 准备生成适配的构建文件（如 `Makefile`）。

## **4. 生成 Makefile 模板Makefile.in：`automake`**

**工具：**
- `automake`：将 `Makefile.am` 转换为 **`Makefile.in`**。
**生成的文件：**
- **`Makefile.in`**：`Makefile` 的模板文件，由 `configure` 使用来生成最终的 `Makefile`。
**作用：**
- 将用户定义的构建规则扩展为标准的 `Makefile` 模板，适配 GNU Make 的标准。

## **5. 配置项目：`./configure`**

**工具：**
- `configure`：检查系统环境并生成适配的 `Makefile`。
**生成的文件：**
- **`Makefile`**：由 `Makefile.in` 模板生成，包含系统适配的编译规则。
**作用：**
- 根据系统环境（如编译器路径、库依赖）生成适合当前系统的 `Makefile`。
- 处理各种平台差异，确保项目能在不同操作系统上正常构建。


## **6. 编译项目：`make`**

**工具：**
- `make`：根据 `Makefile` 中的规则编译项目。
**生成的文件：**
- 可执行文件或库（如 `myprogram`）。
**作用：**
- 将源代码编译为二进制文件，根据 `Makefile` 中的规则构建项目。

## **7. 安装项目：`make install`**

**工具：**
- `make install`：根据 `Makefile` 中的安装规则，将编译结果安装到系统路径。
**作用：**
- 将生成的可执行文件或库复制到系统路径（如 `/usr/local/bin`），供用户使用。

## CMake
**CMake** 是一个跨平台的自动化构建工具，主要用于管理项目的编译过程。它通过使用简单的配置文件 `CMakeLists.txt` 来生成标准构建系统（如 Makefiles 或 Visual Studio 项目）。

- **CMakeLists.txt**：CMake 的配置文件，定义了如何构建项目。
- **Target**：构建目标，通常是一个可执行文件或库。
- **Generator**：CMake 使用生成器来生成具体平台的构建文件，例如 Unix Makefiles 或 Visual Studio 项目文件。
- **Variables**：CMake 中的变量，用于存储和传递信息。

## CMakeLists.txt 语法
```cmake
cmake_minimum_required(VERSION 3.10)  # 指定最低版本要求
project(MyProject)  # 定义项目名称

set(CMAKE_CXX_STANDARD 11)  # 设置 C++ 标准
set(SOURCES main.cpp)  # 设置源文件列表

add_executable(MyExecutable ${SOURCES})  # 定义可执行文件, 需要将这些源文件编译成名为 `MyExecutable` 的可执行文件。

```

- **set()**：设置变量。
- **target_include_directories()**：设置目标的包含目录。


```c
## 定义库文件 Static library
add_library(MyLibrary STATIC MyLibrary.cpp)

## 定义库文件 Shared library
add_library(MyLibrary SHARED MyLibrary.cpp)

## 链接外部库目标
find_package(SomeLibrary REQUIRED)
target_link_libraries(MyExecutable SomeLibrary)

```
使用条件语句和循环
```c
if (WIN32)
    # Windows-specific settings
elseif (UNIX)
    # Unix-specific settings
endif ()

foreach(src IN LISTS SOURCES)
    message(STATUS "Source file: ${src}")
endforeach()

```


### SOURCES
`SOURCES` 变量通常用于存储一组源文件的路径列表。这个列表通常包含项目中所有需要编译的 `.cpp`、`.c` 或其他实现文件的路径。

这里的SOURCES只是一个通常使用的名字，并无特殊意义。使用其他变量名也是没问题的。譬如：
```cmake
set(MY_SOURCES_1 main.cpp)  # 设置源文件列表
```

关键是使用``add_executable(MyExecutable ${MY_SOURCES_1})``或者`add_library(uwa_gpm SHARED ${MY_SOURCES_1})` 配置, 表示这些文件是构建时需要进行编译的源文件。

### GLOB
**可使用GLOB来批量配置SOURCES**
`GLOB` 是一个用于文件查找的命令，它允许您根据指定的模式（模式匹配符）来查找文件，并将这些文件的列表赋值给一个变量。常见的用法是将找到的源文件路径列表赋值给一个变量（例如 `SOURCES`），以便在项目构建过程中使用。

```cmake
file(GLOB MY_SOURCES_1
    "libs/Module/a.cpp"
	"libs/Module/b.cpp"
	"libs/Module/c.cpp"
	"src/*.cpp"
)
```

`file(GLOB_RECURSE ...)` 会递归地查找匹配的文件，包括所有子目录中的文件。用于处理深层次的目录结构。
```cmake
file(GLOB_RECURSE SOURCES "src/*.cpp")
```

### list
可使用list指令追加 `SOURCES` 列表.

```cmake
## 已经定义了主模块源文件
set(SOURCES "main.cpp" "app.cpp")

## 定义其他模块的源文件
set(SOURCES_PLATFORM "platform.cpp" "utils.cpp")

## 将SOURCES_NATIVE_CORE的内容追加到SOURCES
list(APPEND SOURCES ${SOURCES_PLATFORM})

## 现在SOURCES包含 "main.cpp", "app.cpp", "platform.cpp", "utils.cpp"
```


## 头文件与源文件的处理
- **头文件管理**：通过 `include_directories()` 指定头文件所在的目录，使编译器能够找到头文件。头文件目录的配置通常是全局的，适用于整个项目。
- **源文件管理**：通过 `add_executable()` 或 `add_library()` 将源文件添加到构建目标中。CMake 处理源文件列表，并确保它们被编译和链接。

[源文件与头文件的处理](https://www.pystone.net/notes/source-header-file-processing/)

## 项目结构示例
```text
MyProject/
├── CMakeLists.txt
├── src/
│   ├── CMakeLists.txt
│   └── main.cpp
└── include/
    └── MyLibrary.h

```

## CMakeLists配置示例
根目录 CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.10)
project(MyProject)

## 添加子目录
add_subdirectory(src)

## 设置全局包含目录
include_directories(include)
```

src 目录 CMakeLists.txt
```cmake
set(SOURCES main.cpp)

## 添加可执行文件
add_executable(MyExecutable ${SOURCES})
```

静态库与共享库
```cmake
## 静态库
add_library(MyLibrary STATIC src/mylibrary.cpp)
target_include_directories(MyLibrary PUBLIC include)

## 共享库
add_library(MyLibrary SHARED src/mylibrary.cpp)
target_include_directories(MyLibrary PUBLIC include)

## 链接库
target_link_libraries(MyProject PRIVATE MyLibrary)
```

- `add_library` 用于定义库目标，指定库的类型及其组成源文件。
- `target_link_libraries` 用于将一个或多个库或目标链接到一个特定的目标，指定目标的依赖关系。

## 工作流程
- **编写 CMakeLists.txt**：在项目根目录和子目录中编写 `CMakeLists.txt`。
- **配置项目**：使用 CMake 配置项目并生成构建系统文件（例如 Makefile 或 Visual Studio 项目文件）。
```bash
cmake -S <source_dir> -B <binary_dir> # 指定源代码目录和构建输出目录，生成构建系统文件。
cmake -S . -B build/Debug -DCMAKE_BUILD_TYPE=Debug
cmake -S . -B build/Release -DCMAKE_BUILD_TYPE=Release
```

构建文件生成在当前目录，与源代码混合在一起。
```bash
cmake . # 在当前目录生成构建系统文件
cmake -G "Visual Studio 16 2019" .  # 生成 Visual Studio 项目
```

**编译项目**：使用生成的构建系统编译项目。
```bash
cmake --build <binary_dir>
```

清理构建
```sh
cmake --build <binary_dir> --target clean
```

设置构建类型：
```sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
```

## 常用参数

配置和生成相关:
```sh
-S # 指定源代码目录。
-B # 生成的构建文件应该存放的目录
-G # 指定生成器（例如，Unix Makefiles、Ninja、Visual Studio 等）。
-D <var>=<value> # 设置 CMake 缓存变量。
```


编译和安装相关:
- **`--build <dir>`**
    - **作用**：构建已经生成的构建目录中的项目。
    - **示例**：`cmake --build build`
- **`--install <dir>`**
    - **作用**：将构建好的文件安装到指定的目录。
    - **示例**：`cmake --install build --prefix /usr/local`

其他:
`--trace` 在执行过程中跟踪 CMake 命令调用。用于调试和查看 CMake 处理 CMakeLists.txt 文件的详细过程。

## -D传递的变量值
通过 `-D` 选项配置的 CMake 变量可以在 `CMakeLists.txt` 文件中使用。

有一些常用的参数:
**`-DCMAKE_BUILD_TYPE=<type>`**
- **作用**：设置构建类型。
- **值**：常见值包括 `Debug`、`Release`、`RelWithDebInfo`、`MinSizeRel`。

**`-DCMAKE_TOOLCHAIN_FILE=<file>`**
- **作用**：指定工具链文件的路径。
- **用途**：用于交叉编译，设置编译工具链。

**`-DCMAKE_C_COMPILER=<compiler>`**
- **用途**：指定用于编译 C 源代码的编译器。
**`-DCMAKE_CXX_COMPILER=<compiler>`**
- **用途**：指定用于编译 C++ 源代码的编译器。

**`-DCMAKE_INSTALL_PREFIX=`**
- **示例**：`cmake -DCMAKE_INSTALL_PREFIX=/usr/local .`
- **用途**：指定 `make install` 命令安装文件的路径。

**`-DCMAKE_PREFIX_PATH=`**
- **用途**：指定 CMake 在查找依赖包时的搜索路径。

**`-DBUILD_SHARED_LIBS=ON`**
- **用途**：决定构建过程中生成共享库（动态库）还是静态库。

**`-DCMAKE_VERBOSE_MAKEFILE=ON`**
- **作用**：生成详细的 Makefile。
- **示例**：`cmake -DCMAKE_VERBOSE_MAKEFILE=ON .`
- **用途**：在构建过程中显示详细的编译命令。

## Build Type

### 1. Debug
- **特点**：生成包含调试信息的二进制文件，不进行优化。
- **用途**：主要用于开发和调试阶段。调试信息有助于使用调试器进行源码级调试。
- **编译器选项**：
    - GCC: `-g`
    - MSVC: `/Zi`

### 2. Release
- **特点**：生成优化后的二进制文件，不包含调试信息。
- **用途**：主要用于产品发布阶段，追求更高的运行效率和更小的代码体积。
- **编译器选项**：
    - GCC: `-O3`
    - MSVC: `/O2`

### 3. RelWithDebInfo (Release with Debug Information)
- **特点**：生成优化后的二进制文件，同时包含调试信息。
- **用途**：适用于需要在优化后仍能进行调试的场景，常用于性能测试和问题诊断。
- **编译器选项**：
    - GCC: `-O2 -g`
    - MSVC: `/O2 /Zi`

### 4. MinSizeRel (Minimum Size Release)
- **特点**：生成经过大小优化的二进制文件，不包含调试信息。
- **用途**：主要用于对代码大小有严格要求的场景，如嵌入式系统或资源受限的环境。
- **编译器选项**：
    - GCC: `-Os`
    - MSVC: `/O1`

## 生成器
Ninja 是一个专门为速度而设计的小型构建系统。它主要用于生成构建文件，并且通常与 CMake 一起使用来构建软件项目。
- **轻量级构建系统**：Ninja 是一个小型、快速的构建系统，与 GNU Make 类似，但其设计目标是尽可能快速地构建项目。
- **高效**：Ninja 通过最小化磁盘和内存使用，并且只在必要时才运行编译命令，来提高构建速度。这使得它非常适合用于增量构建（即只重新编译那些发生改变的部分）。
- **自动化**：Ninja 不直接编写构建规则，而是依赖其他工具（如 CMake）生成 Ninja 构建文件。

用法：
1. **使用 CMake 生成 Ninja 构建文件**：
```sh
cmake -G Ninja -S <source_dir> -B <build_dir>
```
2. **使用 Ninja 进行构建**：
```xml
ninja -C <build_dir>
```

[Ninja与Makefile比较](https://www.pystone.net/notes/ninja-vs-makefile/)

## Android Studio配置

## Java层调用Native层
* 创建新的 C++ 项目或添加 C++ 支持
	- **新建项目**：在 Android Studio 中创建一个新的项目，选择包含 C++ 支持的模板（如 "Native C++"）。
	- **添加 C++ 支持到现有项目**：如果已有项目没有 C++ 支持，可以通过以下步骤添加：
	    - 右键点击项目根目录，选择 "New" > "File"，命名为 `CMakeLists.txt`。
	    - 在 `app/build.gradle` 文件中添加 `externalNativeBuild` 配置。

- **编写 CMakeLists.txt**：在 `src/main/cpp` 目录下编写 CMakeLists.txt。

```cmake
cmake_minimum_required(VERSION 3.10)
project(MyAndroidProject)

## 设置C++标准
set(CMAKE_CXX_STANDARD 11)

## 添加源文件
set(SOURCES native-lib.cpp)

## 定义共享库 (库名称 库类型 源文件)
add_library(native-lib SHARED ${SOURCES})

## 查找 Android 库 (库名称 库路径)
find_library(log-lib log)

## 链接库
target_link_libraries(native-lib ${log-lib})

## 可以自定义构建类型
## set(CMAKE_BUILD_TYPE Release)
```


项目结构:

```text
MyProject/
├── app/
│   ├── build.gradle
│   ├── src/
│   │   ├── main/
│   │   │   ├── cpp/
│   │   │   │   ├── CMakeLists.txt
│   │   │   │   └── native-lib.cpp
│   │   │   ├── java/
│   │   │   │   └── com/example/myapplication/MainActivity.java
│   │   │   ├── AndroidManifest.xml
│   │   │   ├── res/
│   │   │   └── ...
│   │   └── ...
│   └── ...
└── ...

```

* 在 `src/main/cpp` 目录下创建 `native-lib.cpp` 文件，并编写本地代码

```cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

```


- **配置 build.gradle**：在 `app/build.gradle` 文件中配置 CMake 相关选项。
```groovy
android {
    ...

    defaultConfig {
        ...

        // 设置 NDK 属性
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
        }

        // 设置 ndk 属性,可以配置多个 ABI：
        ndk {
            abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
        }
    }

    // 添加 externalNativeBuild
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

dependencies {
    ...
}

```

* Java调用Native方法
```java
package com.example.myapplication;

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    // 加载本地库
    static {
        System.loadLibrary("native-lib");
    }

    // 声明本地方法
    public native String stringFromJNI();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 调用本地方法并显示结果
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
}

```

## VS Code 配置

## 工作流

### 创建工程
* 编写代码
* 编写CMakeLists.txt

### 配置

使用CMakePresets或者Kits and variants 管理 CMake 配置
configure it to know about the compilers on your system. 有以下两种方案：
* CMake Presets - .json file that let you group settings together
* Kits and variants

#### Preset
Quick Start命令可以快速配置 Presets：
![assets/image-20240724182108673.png](/media/bd665727b1d4b9d6fb49.png)

Presets文件示例：
```json
{
  "version": 3,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 19,
    "patch": 0
  },
  "configurePresets": [
    {
      "name": "default",
      "hidden": true,
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "cacheVariables": {
        "CMAKE_EXPORT_COMPILE_COMMANDS": "YES"
      }
    },
    {
      "name": "debug-armv7",
      "inherits": "default",
      "description": "Build with Debug configuration for ARMv7",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug",
        "ANDROID_ABI": "armeabi-v7a"
      }
    },
    {
      "name": "release-arm64",
      "inherits": "default",
      "description": "Build with Release configuration for ARM64",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "ANDROID_ABI": "arm64-v8a"
      }
    }
  ]
}

```

Presets当中可以配置变量，CMake Tools会将presets中的cacheVariables中的变量解析为命令行的参数。在调用CMake命令时，通过-D的方式将变量值传递。

#### 配置 toolchain 文件
在CMake Kits中配置android.toolchain.cmake

![assets/image-20240726171710860.png](/media/72aa5a675f6ff48983bd.png)
配置好后,直接选这个toolchain即可.
#### 在 `/.vscode/settings.json` 中配置 CMake 参数和编译变种等
```json
{
    "cmake.sourceDirectory": "D:/Repository/GPM-SDK/UwaGpmNative",
	"cmake.buildDirectory": "${workspaceFolder}/build/${variant:buildType}/${variant:abi}",
    "cmake.configureOnOpen": false,
    "cmake.configureSettings": {
        //"CMAKE_SYSTEM_VERSION": "21", // ndk defaultly choose legacy toolchain, this not works
        "ANDROID_PLATFORM": 21,
        "ANDROID_STL": "c++_static",
    },
    "cmake.defaultVariants": {
        "buildType": {
            "default": "debug",
            "description": "The build type.",
            "choices": {
                "debug": {
                    "short": "Debug",
                    "long": "Disable optimizations - include debug information.",
                    "buildType": "Debug"
                },
                "release": {
                    "short": "Release",
                    "long": "Optimize for speed - exclude debug information.",
                    "buildType": "Release"
                },
            }
        },
        "abi": {
            "default": "armeabi-v7a",
            "description": "abi for android build",
            "choices": {
                "armeabi-v7a": {
                    "short": "armv7",
                    "long": "abi for armeabi-v7a",
                    "settings": {
                        "ANDROID_ABI": "armeabi-v7a"
                    }
                },
                "arm64-v8a": {
                    "short": "arm64",
                    "long": "abi for arm64-v8a",
                    "settings": {
                        "ANDROID_ABI": "arm64-v8a"
                    }
                },
            }
        }
    }
}
```
对于settings当中配置的的defaultVariants, 会将其buildType, 在configuration期间翻译为`-DCMAKE_BUILD_TYPE` 选项.

Settings文件当中添加Variants后, 可以通过 Select Variants命令, 选择构建的变种
![assets/image-20240726173225036.png](/media/0d1eabfc7db33ad32009.png)

可以在Settings.json中配置构建目录:
```cpp
"cmake.buildDirectory": "${workspaceFolder}/build/${variant:buildType}/${variant:abi}",
```
这样配置后，你的构建输出将被分类到 `buildType/abi` 目录中，如 `Debug/armeabi-v7a` 和 `Release/arm64-v8a`。

### Configure阶段
执行 **CMake: Configure** ：In CMake, _configure_ refers to detecting requirements and generating the build files that will produce the final compiled artifacts.

The _Generator_ tells CMake what kind of tool will be used to compile and generate the results of the build.

Ninja, Makefile, Visual Studio

### 构建
方法：
- 执行  **CMake: Build**
- 快捷键 F7
- 点击状态栏的 Build 按钮

![assets/image-20240726172001143.png](/media/669be37ba17bd581fe3b.png)
CMake Tools builds by passing the `--build` flag to CMake.

### 调试
