C++ CMake 入门(CMakeLists.txt)

在开发多文件程序时,如果项目里只有两三个源文件,我们直接在终端输入 g++ main.cpp math_utils.cpp -o app 编译还比较轻松。但如果项目扩大到几十甚至上百个 .cpp 文件,每次都在命令行里把所有文件名敲一遍就非常麻烦了。

因此,在实际的 C++ 项目开发中,我们通常会使用 CMake 来自动管理多文件的编译与链接。

CMake 是什么?

CMake 是一个开源、跨平台的项目构建管理工具。需要注意的是,CMake 本身并不是编译器。它不会直接把 .cpp 源代码翻译成机器指令,而是像一位 “编译器的指挥官”,如下图所示。

CMake 与编译器的指挥与执行关系

在使用 CMake 时,我们只需要在项目根目录下写好一份名为 CMakeLists.txt 的配置文件,写清楚项目名称、语言标准以及包含哪些 .cpp 源文件。接下来,CMake 就会根据当前所在的操作系统,自动调用底层编译器完成编译与链接,比如 Windows 下的 MinGW-w64 或 MSVC、Linux 下的 GCC 以及 macOS 下的 Clang。

安装与验证 CMake

如果电脑上还没有安装 CMake,Windows 用户可以从下面的 “官方安装包” 或 “MSYS2 命令” 中任选一种方式完成安装,不需要重复安装两遍。macOS 与 Linux 用户直接前往官网下载或通过系统包管理器安装即可。

1. 使用官方安装包

打开 CMake 官方下载页面,下滑找到 “Binary distributions” 列表,下载对应系统的安装程序,如下图所示。Windows 用户认准后缀为 .msi 的 64 位安装包即可,网页上显示的具体版本号会随官方更新而变化。

在双击运行安装向导时,记得勾选界面上的【Add CMake to the system PATH for all users】选项,让安装程序自动把 CMake 添加到系统环境变量中。

下载 Windows 版本的 CMake 安装包

2. 使用 MSYS2 安装(Windows)

如果之前搭建 C++ 开发环境时已经安装了 MSYS2,那安装起来就更省事了。因为当时我们已经把 C:\msys64\ucrt64\bin 配置到了环境变量中,这里直接打开【MSYS2 UCRT64】终端,输入以下命令并按下 Enter 键即可:

pacman -S mingw-w64-ucrt-x86_64-cmake

终端中输入安装命令的位置如下图所示。执行后如果提示 “[Y/n]” 询问是否继续安装,输入字母 “y” 再按下 Enter 键等待安装完成即可。

在 MSYS2 终端中安装 CMake

3. 验证 CMake 安装

安装完成后,如果之前已经打开了 VS Code 或其他终端窗口,记得先把它们彻底关闭再重新打开,让新的环境变量生效。随后在 VS Code 中新建一个终端窗口,输入 cmake --version 并按下 Enter 键,只要能正常打印出版本号就说明 CMake 已经安装成功了,如下图所示。

在终端验证 CMake 版本

编写第一个 CMakeLists.txt 文件

为了方便小伙伴们快速上手,我们继续沿用前面学习多文件编程时创建的 multi_file_demo 项目来演示自动构建。我们在原有的 3 个代码文件旁边新建一个名为 CMakeLists.txt 的文件,完整目录结构如下:

multi_file_demo/
├── CMakeLists.txt
├── math_utils.h
├── math_utils.cpp
└── main.cpp

为了方便对照测试,这里先列出项目中 3 个代码文件的内容。其中头文件 math_utils.h 代码如下:

#pragma once

int add(int a, int b);
int subtract(int a, int b);

实现文件 math_utils.cpp 代码如下:

#include "math_utils.h"

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

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

主程序文件 main.cpp 代码如下:

#include <iostream>
#include "math_utils.h"

int main() {
    std::cout << "30 + 12 = " << add(30, 12) << '\n';
    std::cout << "30 - 12 = " << subtract(30, 12) << '\n';
    return 0;
}

接下来打开新建的 CMakeLists.txt 文件,写入以下 5 行配置代码并保存:

cmake_minimum_required(VERSION 3.15)
project(MathDemo)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(app main.cpp math_utils.cpp)

这份配置文件一共包含 5 条指令,每条指令的作用如下表所示。

CMake 常用配置指令
CMake 指令 作用说明
cmake_minimum_required(VERSION 3.15) 指定构建该项目所需的 CMake 最低版本,通常写在文件第一行
project(MathDemo) 定义当前项目的名称,这里将项目命名为 MathDemo
set(CMAKE_CXX_STANDARD 20) 指定按 C++20 标准编译代码,如果使用 C++17 则改为 17
set(CMAKE_CXX_STANDARD_REQUIRED ON) 要求编译器必须支持上面指定的语言标准,不支持则直接报错
add_executable(app main.cpp math_utils.cpp) 将 main.cpp 和 math_utils.cpp 编译并链接成可执行程序 app

在上面的配置中,第 3、4 行专门用来锁定项目采用的 C++ 标准,避免不同电脑因编译器默认版本不同而产生报错。

最关键的是最后一行 add_executable()。它的第 1 个参数 app 是最终生成的可执行程序名称,在 Windows 系统下会自动加上后缀生成 app.exe。后面的参数则是参与编译的所有 .cpp 源文件,多个文件名之间用空格或换行隔开即可。

注意 配置文件的名字必须严格写成 CMakeLists.txt,其中 C、M、L 三个字母大写,并且 Lists 末尾带有字母 s,小伙伴们千万不要拼错。至于文件内部的指令名是不区分大小写的,现代 CMake 统一推荐使用 project、add_executable 这种纯小写写法。

使用 CMake 构建与运行项目

写好 CMakeLists.txt 后,如果我们直接在源码文件夹里执行构建,CMake 会生成许多临时文件和缓存文件,把原本干净的代码目录弄得很杂乱。

因此在实际开发中,我们都会采用 “外部构建(Out-of-source build)” 的方式,把所有编译产物统一放进一个独立的 build 文件夹中。整个过程只需要按顺序执行 2 步:① 生成构建配置;② 编译生成程序。

1. 生成构建配置

第 1 步是让 CMake 读取当前目录下的 CMakeLists.txt,并在指定文件夹中生成底层构建规则。

语法:

cmake -B build

说明:

-B 参数用来指定存放编译产物的文件夹名称,通常命名为 build。如果当前项目下还没有 build 文件夹,CMake 会自动创建它,并在里面生成编译器所需的构建文件。

这里 Windows 用户需要特别留意一个细节:在没有安装 Visual Studio 的 Windows 电脑上,直接执行 cmake -B build 可能会报错提示找不到默认生成器。如果我们搭配使用的是前面安装的 MinGW-w64 编译器,首次生成配置时需要加上 -G 参数指定生成器:

cmake -B build -G "MinGW Makefiles"

如果之前漏加 -G 运行报错并留下了残缺的 build 文件夹,我们先把 build 文件夹整个删掉,再重新执行上面这条命令即可。至于 Linux 和 macOS 系统,直接运行 cmake -B build 就能自动识别默认编译器。

2. 编译并运行程序

第 2 步是让 CMake 根据 build 目录中生成的配置,调用底层编译器完成编译与链接。

语法:

cmake --build build

说明:

--build 参数后面紧跟刚才创建的构建目录名 build。执行这条命令后,CMake 就会自动把所有 .cpp 源文件编译并链接成可执行程序。把这两条命令连起来,我们就可以完整构建并运行 multi_file_demo 项目了。

示例:构建并运行多文件项目

下面以 Windows 系统搭配 MinGW-w64 环境为例,我们在终端进入 multi_file_demo 项目根目录,依次执行以下 3 条命令。如果使用的是 Linux 或 macOS 系统,第 1 行省略 -G "MinGW Makefiles",最后一行改为 ./build/app 即可:

cmake -B build -G "MinGW Makefiles"
cmake --build build
.\build\app.exe

运行结果如下。

-- Configuring done
-- Generating done
-- Build files have been written to: D:/multi_file_demo/build
[ 33%] Building CXX object CMakeFiles/app.dir/main.cpp.obj
[ 66%] Building CXX object CMakeFiles/app.dir/math_utils.cpp.obj
[100%] Linking CXX executable app.exe
[100%] Built target app
30 + 12 = 42
30 - 12 = 18

分析:

从终端输出的进度百分比可以清楚看到,执行 cmake --build build 后,CMake 先分别把 main.cpp 和 math_utils.cpp 单独编译成 .obj 目标文件(Linux 或 macOS 下为 .o),随后再把它们链接成可执行程序 app.exe。最后运行 build 目录下的程序,终端就顺利打印出了计算结果 42 和 18。

在阅读一些早期的 CMake 资料时,小伙伴们可能会看到先手动新建文件夹、再切换目录的传统命令。在默认识别编译器的环境下,下面 2 种方式是等价的。

# 方式 1(推荐)
cmake -B build
cmake --build build

# 方式 2
mkdir build
cd build
cmake ..
make

对比可以看出,方式 1 省去了手动新建和切换目录的步骤。同时 cmake --build build 这条编译命令在 Windows、Linux 和 macOS 下完全通用,因此在实际开发中我们优先使用方式 1 即可。

常见问题

1. add_executable() 里面需要把 .h 头文件也写进去吗?

不需要。在 add_executable(app main.cpp math_utils.cpp) 中,我们只需要列出项目用到的 .cpp 源文件即可。因为源文件内部已经写了 #include "math_utils.h",CMake 在构建时会自动扫描并追踪头文件的依赖关系。

当然了,就算把 math_utils.h 一起写了进去,CMake 也不会报错,它会自动跳过头文件而不对其单独编译。

2. 修改了 .cpp 文件里的代码后,每次都要重新执行 cmake -B build 吗?

不需要。如果只是修改了已有 .cpp 或 .h 文件里的代码,我们直接执行第 2 条命令 cmake --build build 就可以了。CMake 会自动检查哪个文件发生了改动,并只重新编译修改过的那个文件。

哪怕我们修改了 CMakeLists.txt,比如往里面添加了新的源文件,直接运行 cmake --build build 时也会自动检测到变化并更新构建规则。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号