CMake 入门教程: 从基础到实践

什么是 CMake?

CMake(全称为 “Cross-Platform Make”)是一种免费并开源的跨平台构建工具, 用于生成构建系统文件(如 Makefile 和 Visual Studio 工程文件), 从而控制软件的编译和链接过程.

为什么选择 CMake?

CMake 为项目工程解决了以下问题:

无论是管理小型项目还是大型复杂工程, CMake 都是现代 C++ 开发中不可或缺的工具.


Modern CMake 的崛起

随着 CMake 的发展, “Modern CMake” 的概念逐渐兴起, 以简洁和模块化的方式管理项目配置, 避免传统 CMake 脚本中的复杂性.

Modern CMake 的特点

  1. 目标驱动构建: 使用 target 概念, 清晰地管理目标(如可执行文件或库)及其属性.

    add_library(my_library src1.cpp src2.cpp)
    target_include_directories(my_library PUBLIC include/)
    target_link_libraries(my_library PRIVATE other_library)
  2. 接口与私有依赖管理: 通过 PUBLIC, PRIVATEINTERFACE 明确依赖范围, 避免全局污染.

  3. 模块化设计: 使用 find_packageFetchContent 管理第三方依赖, 无需手动设置路径.

    include(FetchContent)
    FetchContent_Declare(
        googletest
        URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip
    )
    FetchContent_MakeAvailable(googletest)
  4. 生成器表达式: 简化复杂逻辑表达式, 提高脚本可读性和灵活性.

Modern CMake 极大改进了使用体验, 使其更适应当代软件工程的需求.


CMake 在现代 C++ 生态中的位置

社区支持与流行应用

与主流 IDE 的无缝集成


安装 CMake

使用包管理器安装 CMake

  1. Ubuntu/Debian:

    sudo apt update
    sudo apt install cmake
  2. CentOS/RHEL:

    sudo yum install epel-release
    sudo yum install cmake
  3. macOS:

    brew install cmake
  4. Windows:

    • 使用 winget 安装: winget 是微软提供的一个包管理工具, 作用类似Ubuntu上的apt.

      winget install -e --id Kitware.CMake
    • 手动下载: 访问 CMake 官方下载页面.

从源码安装

当需要最新功能或修复时, 可以从源码安装:

步骤 1: 下载源码

访问 CMake 官网下载页面, 选择最新版本的源码压缩包, 并下载.

例如, 得到 cmake-3.xx.x.tar.gz 文件.

步骤 2: 解压

在命令行中执行:

tar -xzvf cmake-3.xx.x.tar.gz
cd cmake-3.xx.x

步骤 3: 构建并安装

./bootstrap
make
sudo make install

这里:

确认安装成功

在命令行执行以下命令:

cmake --version

应显示安装的版本信息:

cmake version 3.xx.x

如果显示最新版本, 则说明安装成功.

通过连续升级和选择适合版本, 从源码构建可以确保项目最大限度地适配项目构建需求.

样例一: 构建 Hello World

1. 创建项目文件

创建一个目录demo, 包含如下文件:

demo/
├── CMakeLists.txt
└── main.cpp

代码

  1. main.cpp:

    #include <iostream>
    
    int main() {
      std::cout << "Hello, CMake!" << std::endl;
      return 0;
    }
  2. CMakeLists.txt

    # 设置最低的 CMake 版本
    cmake_minimum_required(VERSION 3.10)
    
    # 定义项目名称和语言
    project(CMakeDemo CXX)
    
    # 设置 C++ 标准
    set(CMAKE_CXX_STANDARD 11)
    set(CMAKE_CXX_STANDARD_REQUIRED True)
    
    # 添加可执行文件
    add_executable(demo.exe main.cpp)

    需要注意的是 cmake_minimum_required 是第一句, 必须要指定最低版本要求. 其他的语句则是非常直白的, 命令本身就很清晰.

2. 创建构建目录

创建一个目录用来做构建目录, 即存放编译过程中的临时文件以及最后生成的二进制文件. 这个目录可以任选, 为了符合大多数人用的惯例, 我们就在demo目录下创建一个 build 文件夹.

mkdir build

3. Configure 项目

进入build目录并执行:

cd build
cmake ..

我们将会看到有如下的输出:

-- The CXX compiler identification is GNU 13.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.8s)
-- Generating done (0.0s)
-- Build files have been written to: /home/aronic/playground/CSDNBlogSampleCode/cmake/build

这一步通常被称为"生成项目文件"(Generating Project Files). 在这一步我们可以指定 CMake 生成何种项目, 可以是下面之一:

  1. Unix Makefiles(Linux 默认的生成工具)
  2. Ninja: 跨平台构建工具
  3. Visual Studio 17 2022: 适合 Windows Visual Studio 使用

可以通过-G命令指定.

4. Build 项目

cmake --build .

我们将会看到

[ 50%] Building CXX object CMakeFiles/demo.exe.dir/main.cpp.o
[100%] Linking CXX executable demo.exe
[100%] Built target demo.exe

构建成功之后我们会看到有一个二进制文件demo.exe. 执行该程序:

./demo.exe

输出

Hello, CMake!

样例二: 区分 Debug 版本和 Release 版本

在编译项目的时候通常我们会编译两个版本:

  1. Debug 版本: 适合调试, 测试, 保留符合(代码行数)
  2. Release 版本: 适合在生产环境运行或者发布. 通常会进行优化.

更新CMakeLists.txt, 将其内容改为:

# 设置最低的 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 定义项目名称和版本
project(CMakeDemo VERSION 1.0)

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

# 为不同的编译类型设置编译选项
# Debug 模式下开启调试信息和额外警告
set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -DDEBUG")

# Release 模式下开启优化
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")

# 输出当前的编译类型(仅用于调试)
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

# 添加可执行文件
add_executable(demo.exe main.cpp)

main.cpp改为:

#include <iostream>

int main() {
#ifdef DEBUG
  std::cout << "Running in Debug mode" << std::endl;
#else
  std::cout << "Running in Release mode" << std::endl;
#endif
  return 0;
}

Configure 项目

为了区分不同编译类型, 需要在生成项目的时候指定编译类型CMAKE_BUILD_TYPE:

  1. 编译 Debug 版本

    cmake -DCMAKE_BUILD_TYPE=Debug ..
  2. 编译 Release 版本

    cmake -DCMAKE_BUILD_TYPE=Release ..

编译并运行

在选定了编译类型之后, 可以开始编译, 编译步骤与上一节一致.

# 编译
cmake --build .

# 运行
./demo.exe

Debug版本将会输出:

Running in Debug mode

Release版本将会输出

Running in Release mode

样例三: 链接第三方库

如果我们需要使用一些第三方库来完成一个功能, 比如音视频编解码, 访问 HTTP API, 解析json等等. 在 CMake 中集成第三方库是很方便的, 下面这个例子中我们使用了fmt库, 用来方便的输出容器.

main.cpp文件

#include <fmt/core.h>
#include <fmt/ranges.h>

#include <iostream>
#include <map>
#include <vector>

int main() {
  std::vector<int> vec{1, 2, 3, 4, 5};
  std::map<std::string, std::string> table = {{"name", "cmake"},
                                              {"function", "build"}};

  fmt::println("vec is: {}", vec);
  fmt::println("table is: {}", table);
  return 0;
}

CMakeLists.txt文件为:

# 设置最低的 CMake 版本
cmake_minimum_required(VERSION 3.10)

# 定义项目名称和其使用语言
project(CMakeDemo CXX)

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

# 查找 fmt 库
find_package(fmt CONFIG REQUIRED)

# 添加可执行文件
add_executable(demo.exe main.cpp)
# 链接 fmt 库
target_link_libraries(demo.exe PRIVATE fmt::fmt)

Configure 项目

mkdir build
cd build
cmake ..

编译并运行

# 编译
cmake --build .

# 运行
./demo.exe

输出:

vec is: [1, 2, 3, 4, 5]
table is: {"function": "build", "name": "cmake"}

样例四: 单元测试

测试代码是实际工程中不可缺少的内容. CMake 提供了对测试的支持, 可以使用ctest命令来执行所有测试样例.

  1. 测试文件test.cpp:
#include <gtest/gtest.h>

// 示例测试用例
TEST(SampleTest, Addition) { EXPECT_EQ(1 + 1, 2); }

TEST(SampleTest, Multiplication) { EXPECT_EQ(2 * 3, 6); }
  1. CMakeLists.txt

此处我们使用了FetchContent来获取第三方库. CMake 会自动下载并编译.

# 设置最低的 CMake 版本
cmake_minimum_required(VERSION 3.28)

# 定义项目名称和其使用语言
project(CMakeDemo CXX)

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

enable_testing()
# FetchContent 模块
include(FetchContent)

# 添加 Google Test
FetchContent_Declare(
    googletest
    URL https://github.com/google/googletest/archive/refs/heads/main.zip
)
FetchContent_MakeAvailable(googletest)

# 设置编译选项, 避免在构建测试时污染主工程的设置
set(GTEST_SHUFFLE 1)

# 创建测试可执行文件
add_executable(test.exe test.cpp)

# 链接 Google Test 库
target_link_libraries(test.exe PRIVATE gtest gtest_main)

# 添加测试到 CTest
add_test(NAME test.exe COMMAND test.exe)

Configure 项目

mkdir build
cd build
cmake ..

编译并运行

cmake --build .

编译输出

[ 10%] Building CXX object _deps/googletest-build/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.o
[ 20%] Linking CXX static library ../../../lib/libgtest.a
[ 20%] Built target gtest
[ 30%] Building CXX object _deps/googletest-build/googletest/CMakeFiles/gtest_main.dir/src/gtest_main.cc.o
[ 40%] Linking CXX static library ../../../lib/libgtest_main.a
[ 40%] Built target gtest_main
[ 50%] Building CXX object CMakeFiles/test.exe.dir/test.cpp.o
[ 60%] Linking CXX executable test.exe
[ 60%] Built target test.exe
[ 70%] Building CXX object _deps/googletest-build/googlemock/CMakeFiles/gmock.dir/src/gmock-all.cc.o
[ 80%] Linking CXX static library ../../../lib/libgmock.a
[ 80%] Built target gmock
[ 90%] Building CXX object _deps/googletest-build/googlemock/CMakeFiles/gmock_main.dir/src/gmock_main.cc.o
[100%] Linking CXX static library ../../../lib/libgmock_main.a
[100%] Built target gmock_main

执行测试:

./test.exe

输出

[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from SampleTest
[ RUN      ] SampleTest.Addition
[       OK ] SampleTest.Addition (0 ms)
[ RUN      ] SampleTest.Multiplication
[       OK ] SampleTest.Multiplication (0 ms)
[----------] 2 tests from SampleTest (0 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 2 tests.

总结

CMake 是现代 C++ 构建的基石, 凭借其跨平台能力, 灵活性和强大生态, 成为开源社区和企业的首选构建工具. 从基础到进阶, 学习 CMake 将帮助开发者显著提升构建效率.


学习资源

源码链接

源码链接

Tags: