Ray 的记录站

日常开发实践记录

0%

CMake 基础

根据 cmake-example 这个仓库中的内容编写的这个文档。

CMake 是一个跨平台开源构建系统,可以构建、测试并且打包软件。并且它支持多种本地构建环境,包括 make、Xcode、Visual Studio 等。

基本使用

基本使用中包含如下内容:

  • 最基础的概念
  • 如何管理 Header 文件和源文件
  • 如何创建和使用静态库
  • 如何创建和使用共享库
  • 如何安装编写的程序
  • 如何进行编译配置,如 Debug、Release 等。
  • 额外的编译 flag 配置
  • 如何链接三方库
  • 如何使用 clang 编译器
  • 如何生成 ninja 构建文件
  • 如何使用新的 import 链接三方依赖
  • 如何设置 cpp 标准

概念

如下是一些最基础的概念:

  1. CMakeLists.txt:CMake 通过工程根目录或子目录下的 CMakeLists.txt 进行配置,在其中保存 CMake 的各种指令。如何运行 CMake 的目录中不存在这个文件,则会报错。
  2. 设置最低 CMake 版本:
    1
    cmake_minimum_required(VERSION 3.5)
  3. 工程信息配置,工程名称和版本等:
    1
    project(NameOfMyProject VERSION 0.1.0)
  4. 定义 Target:通过 Target 定义来决定哪些文件应该被编译为什么样的产物
    1
    2
    3
    4
    5
    # 定义一个可执行程序 Target,并且指定它需要包含的源文件
    add_executable(TargetName source1.cpp source2.cpp)

    # 变量 PROJECT_NAME(即工程名称)作为 Target 名字
    add_executable(${PROJECT_NAME} source1.cpp)
  5. CMake 有两种工作方式,一种称为 “In-Place”,另外一种是 “Out-of-Source”。前者会将编译相关的产物放到和源码目录一起,而后者专门建立一个文件夹存放编译构建时候的相关产物。目前一般都是使用后一种方式。
  6. Binary Directory:运行 CMake 命令时候的根目录或顶层目录。这个目录可以通过变量 CMAKE_BINARY_DIR 获取。
  7. 手动执行 “Out-of-Source” 构建:
    简单的工程结构如下:
    1
    2
    ├── CMakeLists.txt
    ├── main.cpp
    手动构建的流程如下,在工程根目录下进行:
    1
    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
2
3
4
5
6
├── CMakeLists.txt
├── include
│ └── Hello.h
└── src
├── Hello.cpp
└── main.cpp

如下是一些常用的变量:

变量名 含义
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
2
3
4
5
6
set(SOURCES
src/Hello.cpp
src/main.cpp
)

add_executable(${PROJECT_NAME} ${SOURCES})

更老的做法有使用 GLOB 的:

1
2
# 通过 GLOB 指令将源码列表存放到自定义的 SOURCES 变量中
file(GLOB SOURCES "src/*.cpp")

新版 CMake 不推荐上述做法,而是直接将源码列到 add_xxx 里面,类似如下:

1
2
3
4
add_executable(${PROJECT_NAME}     
src/Hello.cpp
src/main.cpp
)

设置 Including 目录

当有多个不同的 include 目录时,可以使用 target_include_directories() 函数设置这些目录。当编译对应 target 时,cmake 会自动将这些目录传到编译器(通过 -I 标志,比如 -I/directory/path):

1
2
3
4
target_include_directories(target
PRIVATE
${PROJECT_SOURCE_DIR}/include
)

PRIVATE 表示这个目录是 Target 的私有 Header(概念上同 Xcode 中的 Target Header)。可用的 Scope 有:INTERFACE|PUBLIC|PRIVATE 。其中:

  • PUBLIC:默认,使用它标记的所有目录中的 header 可被 Target 或外界使用(如果外界依赖该 Target)。
    这些目录会被追加到 INCLUDE_DIRECTORIESINTERFACE_INCLUDE_DIRECTORIES
  • INTERFACE:当前 Target 无法使用,但可被外界依赖者使用。
    这些目录会被追加到 INTERFACE_INCLUDE_DIRECTORIES
  • PRIVATE:外界无法使用,仅被 Target 本身使用。
    这些目录会被追加到 INCLUDE_DIRECTORIES

静态库

一个简单静态库的工程文件结构如下:

1
2
3
4
5
6
7
├── CMakeLists.txt
├── include
│ └── static
│ └── Hello.h
└── src
├── Hello.cpp
└── main.cpp

当然如果是实际工程,一般采用 子工程 的方式组织工程内的静态库 Target,这里是简化的例子。

使用 add_library() 函数定义库 Target:

1
2
3
add_library(hello_library STATIC
src/Hello.cpp
)

这里使用现代化的方式,将该静态库关联的源文件放到 add_xxx 函数中。cmake 会将这个 Target 编译为名为 libhello_library.a 的静态库。

此外,静态库的 Header 通过下面的方式设置:

1
2
3
4
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)

如果工程中有其他 Target,则可以像下面这样使用该静态库:

1
2
3
4
5
6
7
8
add_executable(hello_binary
src/main.cpp
)

target_link_libraries( hello_binary
PRIVATE
hello_library
)

完整的 cmake:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
cmake_minimum_required(VERSION 3.5)

project(hello_library)

############################################################
# Create a library
############################################################

#Generate the static library from the library sources
add_library(hello_library STATIC
src/Hello.cpp
)

target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)


############################################################
# Create an executable
############################################################

# Add an executable with the above sources
add_executable(hello_binary
src/main.cpp
)

# link the new hello_library target with the hello_binary target
target_link_libraries( hello_binary
PRIVATE
hello_library
)