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 cmake
或yum install cmake
安装完成后,可以通过命令行验证:
cmake --version
第一个 CMake 项目
让我们从一个简单的 "Hello World" 程序开始:
- 创建项目文件夹结构:
hello_cmake/
├── CMakeLists.txt
└── main.cpp
main.cpp
文件内容:
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
CMakeLists.txt
文件内容:
# 设置 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)
- 构建项目:
# 创建构建目录
mkdir build
cd build
# 生成构建系统
cmake ..
# 执行构建
cmake --build .
- 运行程序:
# 在 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
:
#pragma once
class Calculator {
public:
int add(int a, int b);
int subtract(int a, int b);
};
src/calculator.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
:
#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_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_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_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_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
:
# 创建库
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
:
# 查找 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 模块获取它。
构建和运行测试
mkdir build && cd build
cmake ..
cmake --build .
ctest
CMake 最佳实践
目标属性而非全局变量
尽量使用目标特定的命令而不是全局设置:
# 不推荐
include_directories(include)
add_executable(myapp main.cpp)
# 推荐
add_executable(myapp main.cpp)
target_include_directories(myapp PRIVATE include)
使用模块化 CMake 文件
对于复杂项目,将 CMake 文件分解成多个模块:
# 在主 CMakeLists.txt 中
include(cmake/FindDependencies.cmake)
include(cmake/CompilerSettings.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 代码的质量问题
- ccmake 或 cmake-gui: 提供交互式配置界面
故障排除
常见问题与解决方案
-
找不到包
问题:
Could not find package SomeLib
解决:使用
CMAKE_PREFIX_PATH
指定库的安装路径bashcmake -DCMAKE_PREFIX_PATH=/path/to/somelib ..
-
编译器不支持指定的 C++ 标准
问题:
Compiler does not support -std=c++17
解决:降低 C++ 标准或更新编译器
cmakeset(CMAKE_CXX_STANDARD 14)
-
链接错误
问题:
Undefined reference to function
解决:确保正确链接了所有必要的库
cmaketarget_link_libraries(myapp PRIVATE required_lib)
总结
CMake 是 C++ 项目中不可或缺的构建工具,它提供了一个强大、灵活且跨平台的解决方案来管理复杂的构建过程。本文介绍了:
- CMake 的基本概念和安装
- 创建基本 CMake 项目
- 处理多文件项目和库
- 添加和使用外部依赖
- 使用 CMake 的最佳实践
掌握 CMake 将极大地提高你的 C++ 开发效率,特别是在处理大型项目或需要跨平台支持时。
练习
- 创建一个包含多个源文件的基本 C++ 项目,并使用 CMake 构建它。
- 将项目的一部分功能抽取为一个库,然后在主程序中使用这个库。
- 在项目中添加一个外部依赖(如 fmt 或 nlohmann_json 库)。
- 为项目添加单元测试,并确保它们可以通过 CTest 运行。
- 尝试为你的项目创建一个安装目标,并指定安装规则。