CMake 基础
本文最后更新于 2023年7月28日 上午
根据 cmake-example 这个仓库中的内容编写的这个文档。
CMake 是一个跨平台开源构建系统,可以构建、测试并且打包软件。并且它支持多种本地构建环境,包括 make、Xcode、Visual Studio 等。
基本使用
基本使用中包含如下内容:
- 最基础的概念
- 如何管理 Header 文件和源文件
- 如何创建和使用静态库
- 如何创建和使用共享库
- 如何安装编写的程序
- 如何进行编译配置,如 Debug、Release 等。
- 额外的编译 flag 配置
- 如何链接三方库
- 如何使用 clang 编译器
- 如何生成 ninja 构建文件
- 如何使用新的 import 链接三方依赖
- 如何设置 cpp 标准
概念
如下是一些最基础的概念:
CMakeLists.txt
:CMake 通过工程根目录或子目录下的CMakeLists.txt
进行配置,在其中保存 CMake 的各种指令。如何运行 CMake 的目录中不存在这个文件,则会报错。- 设置最低 CMake 版本:
1
cmake_minimum_required(VERSION 3.5)
- 工程信息配置,工程名称和版本等:
1
project(NameOfMyProject VERSION 0.1.0)
- 定义 Target:通过 Target 定义来决定哪些文件应该被编译为什么样的产物
1
2
3
4
5# 定义一个可执行程序 Target,并且指定它需要包含的源文件
add_executable(TargetName source1.cpp source2.cpp)
# 变量 PROJECT_NAME(即工程名称)作为 Target 名字
add_executable(${PROJECT_NAME} source1.cpp) - CMake 有两种工作方式,一种称为 “In-Place”,另外一种是 “Out-of-Source”。前者会将编译相关的产物放到和源码目录一起,而后者专门建立一个文件夹存放编译构建时候的相关产物。目前一般都是使用后一种方式。
- Binary Directory:运行 CMake 命令时候的根目录或顶层目录。这个目录可以通过变量
CMAKE_BINARY_DIR
获取。 - 手动执行 “Out-of-Source” 构建:
简单的工程结构如下:手动构建的流程如下,在工程根目录下进行:1
2├── CMakeLists.txt
├── main.cpp1
2
3
4
5
6
7
8
9
10
11#1. 创建 build 目录
mkdir build
#2. 进入目录
cd build
#3. 调用 cmake 命令生成 make 相关配置(cmake 默认生成 make 构建配置文件)
cmake ..
#4. 进行构建
make当然上述过程如果使用 VSCode 的 CMake 插件时都是自动进行的。
分离源码和 Header 文件到不同目录
可以将工程组织成如下方式:
1 |
|
如下是一些常用的变量:
变量名 | 含义 |
---|---|
CMAKE_SOURCE_DIR | 源码根(root)目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前的源码目录(若使用子工程或子目录) |
PROJECT_SOURCE_DIR | 当前 cmake 工程的源码目录 |
CMAKE_BINARY_DIR | 二进制根目录,这个目录是运行 cmake 命令时候的根目录。 |
CMAKE_CURRENT_BINARY_DIR | 当前所在的 build 目录。 |
PROJECT_BINARY_DIR | 当前工程对应的 build 目录。 |
将源码存放到变量中
通过如下方式可以将源码存放到变量中,便于用在不同的地方:
1 |
|
更老的做法有使用 GLOB
的:
1 |
|
新版 CMake 不推荐上述做法,而是直接将源码列到 add_xxx
里面,类似如下:
1 |
|
设置 Including 目录
当有多个不同的 include 目录时,可以使用 target_include_directories()
函数设置这些目录。当编译对应 target 时,cmake 会自动将这些目录传到编译器(通过 -I
标志,比如 -I/directory/path
):
1 |
|
PRIVATE
表示这个目录是 Target 的私有 Header(概念上同 Xcode 中的 Target Header)。可用的 Scope
有:INTERFACE|PUBLIC|PRIVATE
。其中:
PUBLIC
:默认,使用它标记的所有目录中的 header 可被 Target 或外界使用(如果外界依赖该 Target)。
这些目录会被追加到INCLUDE_DIRECTORIES
和INTERFACE_INCLUDE_DIRECTORIES
。INTERFACE
:当前 Target 无法使用,但可被外界依赖者使用。
这些目录会被追加到INTERFACE_INCLUDE_DIRECTORIES
PRIVATE
:外界无法使用,仅被 Target 本身使用。
这些目录会被追加到INCLUDE_DIRECTORIES
。
静态库
一个简单静态库的工程文件结构如下:
1 |
|
当然如果是实际工程,一般采用 子工程 的方式组织工程内的静态库 Target,这里是简化的例子。
使用 add_library()
函数定义库 Target:
1 |
|
这里使用现代化的方式,将该静态库关联的源文件放到 add_xxx
函数中。cmake 会将这个 Target 编译为名为 libhello_library.a 的静态库。
此外,静态库的 Header 通过下面的方式设置:
1 |
|
如果工程中有其他 Target,则可以像下面这样使用该静态库:
1 |
|
完整的 cmake:
1 |
|