CMake 入门教程之一

大纲

CMake 基本概念

什么是 CMake

CMake 是用于构建、测试和软件打包的开源跨平台工具。

为什么要学习 CMake

  • 企业项目:不管是构建 Linux 程序还是自动化构建 VS 程序,业内大量公司都在使用 CMake。
  • 开源项目:QT、OpenCV、GoogleTest、KDE、OGRE、Android NDK、鸿蒙 ETS NDK 等知名开源项目都使用了 CMake。
  • 职业发展:CMake 适用于跨平台自动化构建和部署、持续集成、测试驱动开发、自动化单元测试等场景。

为什么要选用 CMake

  • 为什么需要一个好的构建系统

    • 想避免硬编码路径
    • 需要在多台计算机上构建一个包
    • 想使用 CI(持续集成)
    • 需要支持不同的操作系统
    • 想支持多个编译器
    • 想使用 IDE,但不是所有情况
    • 想描述程序的逻辑结构,而不是标志和命令
    • 想使用库
    • 想使用其他工具来帮助编写代码,比如 ProtoBuf
    • 想使用单元测试
  • 为什么需要持续集成

    • 每次集成都通过自动化的制造(包括提交、发布、自动化测试)来验证,准确地发现集成错误
    • 快速错误,每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易
    • 各种不同的更新主干,如果不经常集成,会导致集成的成本变大
    • 让产品可以快速地通过,同时保持关键测试合格
    • 自动化测试,只要有一个测试用例不通过就不能集成
    • 集成并不能删除发现的错误,但可以让它们很容易被发现和改正
  • 为什么是 CMake

    • CMake 的特性
      • 自动搜索可能需要的程序、库和头文件的能力
      • 独立的构建目录,可以安全清理
      • 支持创建复杂的自定义命令,例如 qt moc uic
      • 配置时选择可选组件的能力
      • 从简单的文本文件(CMakeLists.txt)自动生成工作区和项目的能力
      • 在静态和共享构建之间轻松切换的能力
      • 在大多数平台上自动生成文件依赖项,并支持并行构建
    • 每个 IDE 都支持 CMake( CMake 支持几乎所有 IDE)
    • 使用 CMake 的软件包比任何其他系统都多

CMake 的工作原理

编译第一个 CMake 项目

  • 项目的目录结构
1
2
3
first_cmake
├── CMakeLists.txt
└── first_cmake.cpp
  • first_cmake.cpp 源文件
1
2
3
4
5
6
7
8
#include <iostream>

using namespace std;

int main (int argc, char *argv []) {
cout << "First CMake Test" << endl;
return 0;
}
  • CMakeLists.txt 配置文件
1
2
3
4
5
6
7
8
# 指定 CMake 版本
cmake_minimum_required (VERSION 3.17)

# 指定项目名称
project (first_cmake)

# 指定要编译的源文件
add_executable (first_cmake first_cmake.cpp)
  • 执行 CMake 编译
1
2
3
4
5
# 配置项目,生成构建文件(例如 Makefile 或 Ninja 文件)
cmake -S . -B build

# 编译项目,生成可执行文件
cmake --build build
1
2
3
4
- 参数说明
- `-S`:指定从哪里获取 `CMakeLists.txt` 配置文件,`.` 表示当前目录
- `-B`:指定生成的构建文件应该存放的目录(通常指定为 `build` 目录),默认会存放在当前目录下
- `--build`:指定从哪里获取构建文件进行编译,或者说是指定编译后的输出目录
  • 上述两个 CMake 命令的功能可以分为几部分:
    • 调用生成的构建系统:
      • 如果之前生成的是 Makefile,它将调用 make
      • 如果生成的是 Ninja 构建系统,它将调用 ninja
    • 编译项目:
      • 它将编译项目中的源代码,并生成可执行文件、库等目标。
    • 自动检测编译工具:
      • cmake --build 会根据生成的构建系统(Makefile、Ninja 等)来自动使用正确的工具进行构建,而不需要手动调用 makeninja

提示

通过 cmake --build build,可以避免使用特定的生成器工具(例如 makeninja),并保持跨平台的一致性。

使用指定的 C/C++ 编译器

在使用 CMake 编译 C/C++ 项目时,CMake 会自动根据不同的操作系统类型来调用对应的 C/C++ 编译器,比如:

  • Linux 操作系统:使用 gccg++clang 编译器。
  • Windows 操作系统:使用 Microsoft Visual C++ 编译器(简称 MSVC),提供了 cl.exe 编译和 link.exe 链接等命令行工具。

在 Windows 操作系统上,如果不是在 Developer Command Prompt for Visual Studio 开发者命令提示符窗口内执行生成构建文件的 CMake 命令,而是在 Git Bash 内执行命令,那么就会出现以下的错误信息:

1
2
# 配置项目,生成构建文件(例如 Makefile 或 Ninja 文件)
cmake -S . -B build
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- Building for: NMake Makefiles
CMake Error at CMakeLists.txt:5 (project):
Running

'nmake' '-?'

failed with:

no such file or directory


CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!

这通常是因为在 Git Bash 内运行 CMake 时,CMake 找不到 nmake 工具,比如 nmake 工具不可用或没有正确安装。解决该问题的方法是,在 Git Bash 中让 CMake 使用特定的 C/C++ 编译器。

什么是 nmake

nmake 是 Microsoft 的一个构建工具,其作用类似 make,它通常与 Visual Studio 一起安装,尤其在使用 Microsoft Visual C++ 编译器(简称 MSVC)时,CMake 可能需要它来生成配置文件。

解决方案一

在 Windows 系统上 安装 MinGW,然后在 Git Bash 中让 CMake 使用 MinGW 自带的 gccg++ 编译器。

  • 首先删除之前生成的 build 目录
1
2
# 删除构建目录
rm -rf build
  • 在 Git Bash 中,要让 CMake 使用 MinGW 提供的 gccg++ 编译器,可以通过指定使用 MinGW Makefiles 配置生成器来完成
1
2
# 配置项目,生成构建文件(例如 Makefile 或 Ninja 文件)
cmake -S . -B build -G "MinGW Makefiles"
  • 一旦配置完成,就可以使用以下命令编译 C/C++ 项目,相当于执行了 mingw32-make -C build 命令
1
2
# 编译项目,生成可执行文件
cmake --build build
1
2
3
4
- 参数说明
- `-S`:指定从哪里获取 `CMakeLists.txt` 配置文件,`.` 表示当前目录
- `-B`:指定生成的构建文件应该存放的目录(通常指定为 `build` 目录),默认会存放在当前目录下。
- `-G`:指定 CMake 使用哪个配置生成器,比如使用 MinGW Makefiles。

解决方案二

在 Windows 系统上 安装 MinGW,然后在 Git Bash 中让 CMake 使用 MinGW 自带的 gccg++ 编译器。

  • 首先删除之前生成的 build 目录
1
2
# 删除构建目录
rm -rf build
  • 在 Git Bash 中,要让 CMake 使用 MinGW 提供的编译器,还可以在 CMakeLists.txt 配置文件中,显式指定 C/C++ 编译器
1
2
3
4
5
# 设置 MinGW 提供的 GCC 作为 C 编译器
set (CMAKE_C_COMPILER "C:/MinGW/bin/gcc.exe")

# 设置 MinGW 提供的 G++ 作为 C++ 编译器
set (CMAKE_CXX_COMPILER "C:/MinGW/bin/g++.exe")

然后编译项目代码,必须确保上面设置的编译器路径和 MinGW 的安装路径一致

1
2
3
4
5
# 配置项目,生成构建文件(例如 Makefile 或 Ninja 文件)
cmake -S . -B build

# 编译项目,生成可执行文件
cmake --build build

成功编译代码后,最后会显示 gccg++ 编译器都来自 MinGW,而不是 Visual Studio 的 cl.exe

特别注意

笔者使用该方案来让 CMake 使用特定的编译器,但最终没有成功,原因暂时未知。

使用 NMake 构建并编译

在 Windows 环境下,若希望让 CMake 使用 NMake 构建并编译项目,可以在 Developer Command Prompt for Visual Studio 开发者命令提示符窗口内执行以下命令:

  • 让 CMake 使用 NMake Makefiles 配置生成器来生成构建文件
1
2
# 配置项目,生成构建文件(例如 Makefile 或 Ninja 文件)
cmake -S . -B build -G "NMake Makefiles"
  • 一旦配置完成,就可以使用以下命令编译 C/C++ 项目,相当于在 build 目录下执行 nmake 命令
1
2
# 编译项目,生成可执行文件
cmake --build build
1
2
3
4
- 参数说明
- `-S`:指定从哪里获取 `CMakeLists.txt` 配置文件,`.` 表示当前目录。
- `-B`:指定生成的构建文件应该存放的目录(通常指定为 `build` 目录),默认会存放在当前目录下。
- `-G`:指定 CMake 使用哪个配置生成器,比如使用 NMake Makefiles。

提示

  • 若想知道当前版本的 CMake 支持哪些生成器(Generators),可以使用帮助命令 cmake --help 来查看详细的帮助手册。
  • CMake 常用的生成器有:NMake Makefiles、MinGW Makefiles、MSYS Makefiles、Ninja 等。

使用常见的编译选项

  • --target <target>: 指定要构建的特定目标(例如,生成特定的可执行文件或库)。如果不指定,它会构建所有默认目标。
1
cmake --build build --target my_target
  • --config <configuration>: 用于多配置生成器(例如 Visual Studio 或 Xcode),可以指定构建的配置类型,如 DebugRelease
1
cmake --build build --config Release
  • --clean-first: 先清理项目再编译,相当于 make clean && make
1
cmake --build build --clean-first