跳到主要内容

C++ CMake

CMake 简介

CMake 是一个开源的跨平台自动化构建系统,它使用与平台和编译器无关的配置文件来控制软件编译过程,可以生成标准的构建文件(如 Makefile 或 Visual Studio 项目文件)。在 C++ 项目开发中,CMake 已成为事实上的标准构建工具,尤其适用于中大型项目。

为什么需要 CMake?

当我们的 C++ 项目变得越来越复杂时,手动管理编译过程会变得非常困难:

  • 多个源文件需要编译
  • 依赖库需要链接
  • 不同平台需要不同的编译选项
  • 第三方库的管理

CMake 通过一种简单的声明式语法解决这些问题,让开发者能专注于代码而不是构建系统。

提示

CMake 不是直接的构建系统,而是一个构建系统的生成器。它会生成你选择的构建系统(如 Make、Ninja、Visual Studio 等)的输入文件。

CMake 基础

安装 CMake

在开始之前,你需要先安装 CMake:

  • Windows: 从 CMake 官网 下载安装程序
  • macOS: 使用 Homebrew: brew install cmake
  • Linux: 使用包管理器,如 apt install cmakeyum install cmake

安装完成后,可以通过命令行验证:

bash
cmake --version

第一个 CMake 项目

让我们从一个简单的 "Hello World" 程序开始:

  1. 创建项目文件夹结构:
hello_cmake/
├── CMakeLists.txt
└── main.cpp
  1. main.cpp 文件内容:
cpp
#include <iostream>

int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
  1. CMakeLists.txt 文件内容:
cmake
# 设置 CMake 最低版本要求
cmake_minimum_required(VERSION 3.10)

# 设置项目名称
project(HelloCMake)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加可执行文件
add_executable(hello_cmake main.cpp)
  1. 构建项目:
bash
# 创建构建目录
mkdir build
cd build

# 生成构建系统
cmake ..

# 执行构建
cmake --build .
  1. 运行程序:
bash
# 在 Linux/macOS
./hello_cmake

# 在 Windows
hello_cmake.exe

输出:

Hello, CMake!

CMakeLists.txt 核心语法

这里是一些最常用的 CMake 命令:

  • cmake_minimum_required(VERSION x.y): 指定所需的 CMake 最低版本
  • project(项目名): 定义项目名称
  • add_executable(可执行文件名 源文件): 指定要从哪些源文件构建可执行文件
  • add_library(库名 [STATIC|SHARED|MODULE] 源文件): 从源文件创建库
  • target_link_libraries(目标 库): 将库链接到目标
  • target_include_directories(目标 [PUBLIC|PRIVATE|INTERFACE] 目录): 添加包含目录
  • set(变量名 值): 设置 CMake 变量

进阶 CMake 用法

多文件项目

现在我们升级一下项目结构,包括多个源文件和头文件:

calculator/
├── CMakeLists.txt
├── include/
│ └── calculator.h
└── src/
├── calculator.cpp
└── main.cpp

文件内容:

  • include/calculator.h:
cpp
#pragma once

class Calculator {
public:
int add(int a, int b);
int subtract(int a, int b);
};
  • src/calculator.cpp:
cpp
#include "calculator.h"

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

int Calculator::subtract(int a, int b) {
return a - b;
}
  • src/main.cpp:
cpp
#include <iostream>
#include "calculator.h"

int main() {
Calculator calc;
std::cout << "5 + 3 = " << calc.add(5, 3) << std::endl;
std::cout << "5 - 3 = " << calc.subtract(5, 3) << std::endl;
return 0;
}
  • CMakeLists.txt:
cmake
cmake_minimum_required(VERSION 3.10)
project(CalculatorProject)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加头文件目录
include_directories(include)

# 添加所有源文件
set(SOURCES
src/calculator.cpp
src/main.cpp
)

# 创建可执行文件
add_executable(calculator ${SOURCES})

构建库和使用库

现在我们将计算器功能作为库,然后在主程序中使用它:

CMakeLists.txt:

cmake
cmake_minimum_required(VERSION 3.10)
project(CalculatorProject)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加库
add_library(calculator_lib src/calculator.cpp)
target_include_directories(calculator_lib PUBLIC include)

# 添加可执行文件并链接库
add_executable(calculator src/main.cpp)
target_link_libraries(calculator PRIVATE calculator_lib)

添加外部依赖

在实际项目中,我们经常需要使用第三方库。以下是如何在 CMake 中添加和使用一个外部库(以 Boost 为例):

cmake
cmake_minimum_required(VERSION 3.10)
project(BoostExample)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 寻找 Boost 库
find_package(Boost REQUIRED COMPONENTS filesystem system)

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

# 链接 Boost 库
if(Boost_FOUND)
target_include_directories(boost_example PRIVATE ${Boost_INCLUDE_DIRS})
target_link_libraries(boost_example PRIVATE ${Boost_LIBRARIES})
endif()

实际案例:构建一个带有测试的项目

下面是一个更完整的项目示例,包含源代码、库、测试和安装规则:

project/
├── CMakeLists.txt
├── include/
│ └── mylib/
│ └── myclass.h
├── src/
│ ├── CMakeLists.txt
│ ├── myclass.cpp
│ └── main.cpp
└── tests/
├── CMakeLists.txt
└── test_myclass.cpp

CMakeLists.txt:

cmake
cmake_minimum_required(VERSION 3.14)
project(CompleteProject VERSION 1.0.0)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 启用测试
enable_testing()

# 添加子目录
add_subdirectory(src)
add_subdirectory(tests)

# 安装规则
install(TARGETS myapp DESTINATION bin)
install(TARGETS mylib DESTINATION lib)
install(DIRECTORY include/ DESTINATION include)

src/CMakeLists.txt:

cmake
# 创建库
add_library(mylib myclass.cpp)
target_include_directories(mylib PUBLIC ${CMAKE_SOURCE_DIR}/include)

# 创建可执行文件
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)

tests/CMakeLists.txt:

cmake
# 查找 Google Test
find_package(GTest REQUIRED)

# 添加测试可执行文件
add_executable(test_myclass test_myclass.cpp)
target_link_libraries(test_myclass PRIVATE mylib GTest::GTest GTest::Main)

# 添加测试
add_test(NAME MyClassTests COMMAND test_myclass)
备注

这个例子使用了 Google Test,你需要事先安装它或通过 CMake 的 FetchContent 模块获取它。

构建和运行测试

bash
mkdir build && cd build
cmake ..
cmake --build .
ctest

CMake 最佳实践

目标属性而非全局变量

尽量使用目标特定的命令而不是全局设置:

cmake
# 不推荐
include_directories(include)
add_executable(myapp main.cpp)

# 推荐
add_executable(myapp main.cpp)
target_include_directories(myapp PRIVATE include)

使用模块化 CMake 文件

对于复杂项目,将 CMake 文件分解成多个模块:

cmake
# 在主 CMakeLists.txt 中
include(cmake/FindDependencies.cmake)
include(cmake/CompilerSettings.cmake)

指定编译选项

cmake
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra>
)

使用 CMake 工具

  • cmake-format: 可以格式化 CMakeLists.txt 文件
  • cmake-lint: 可以检查 CMake 代码的质量问题
  • ccmakecmake-gui: 提供交互式配置界面

故障排除

常见问题与解决方案

  1. 找不到包

    问题:Could not find package SomeLib

    解决:使用 CMAKE_PREFIX_PATH 指定库的安装路径

    bash
    cmake -DCMAKE_PREFIX_PATH=/path/to/somelib ..
  2. 编译器不支持指定的 C++ 标准

    问题:Compiler does not support -std=c++17

    解决:降低 C++ 标准或更新编译器

    cmake
    set(CMAKE_CXX_STANDARD 14)
  3. 链接错误

    问题:Undefined reference to function

    解决:确保正确链接了所有必要的库

    cmake
    target_link_libraries(myapp PRIVATE required_lib)

总结

CMake 是 C++ 项目中不可或缺的构建工具,它提供了一个强大、灵活且跨平台的解决方案来管理复杂的构建过程。本文介绍了:

  • CMake 的基本概念和安装
  • 创建基本 CMake 项目
  • 处理多文件项目和库
  • 添加和使用外部依赖
  • 使用 CMake 的最佳实践

掌握 CMake 将极大地提高你的 C++ 开发效率,特别是在处理大型项目或需要跨平台支持时。

练习

  1. 创建一个包含多个源文件的基本 C++ 项目,并使用 CMake 构建它。
  2. 将项目的一部分功能抽取为一个库,然后在主程序中使用这个库。
  3. 在项目中添加一个外部依赖(如 fmt 或 nlohmann_json 库)。
  4. 为项目添加单元测试,并确保它们可以通过 CTest 运行。
  5. 尝试为你的项目创建一个安装目标,并指定安装规则。

进一步学习的资源