什么是 CMake?
CMake(全称为 “Cross-Platform Make”)是一种免费并开源的跨平台构建工具, 用于生成构建系统文件(如 Makefile 和 Visual Studio 工程文件), 从而控制软件的编译和链接过程.
为什么选择 CMake?
CMake 为项目工程解决了以下问题:
- 跨平台构建: 支持为多种平台生成配置文件, 如 Linux 上的 Makefile 和 Windows 上的 Visual Studio 工程.
- 适用于复杂项目: 便于管理含有多个子项目和层级的大型工程.
- 自动化和通用配置: 通过
CMakeLists.txt
文件轻松管理依赖和配置, 大幅简化流程.
无论是管理小型项目还是大型复杂工程, CMake 都是现代 C++ 开发中不可或缺的工具.
Modern CMake 的崛起
随着 CMake 的发展, “Modern CMake” 的概念逐渐兴起, 以简洁和模块化的方式管理项目配置, 避免传统 CMake 脚本中的复杂性.
Modern CMake 的特点
-
目标驱动构建:
使用target
概念, 清晰地管理目标(如可执行文件或库)及其属性.add_library(my_library src1.cpp src2.cpp) target_include_directories(my_library PUBLIC include/) target_link_libraries(my_library PRIVATE other_library)
-
接口与私有依赖管理:
通过PUBLIC
,PRIVATE
和INTERFACE
明确依赖范围, 避免全局污染. -
模块化设计:
使用find_package
和FetchContent
管理第三方依赖, 无需手动设置路径.include(FetchContent) FetchContent_Declare( googletest URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip ) FetchContent_MakeAvailable(googletest)
-
生成器表达式:
简化复杂逻辑表达式, 提高脚本可读性和灵活性.
Modern CMake 极大改进了使用体验, 使其更适应当代软件工程的需求.
CMake 在现代 C++ 生态中的位置
社区支持与流行应用
- 广泛支持: 许多开源项目(如 LLVM, Qt 和 OpenCV)选择 CMake 作为首选构建工具.
- 工具集成: 与包管理工具(如 Conan 和 vcpkg)深度集成, 简化依赖管理.
- 适应性强: 从小型库到大型复杂项目, CMake 都能高效管理.
与主流 IDE 的无缝集成
- Visual Studio: 内置支持, 用户可直接打开
CMakeLists.txt
文件. - CLion: JetBrains CLion 将 CMake 作为默认构建系统, 提供出色的开发体验.
- Xcode: 支持生成适配 macOS 的工程文件.
- VSCode: 通过
CMake Tools
插件, 提供调试, 配置和目标管理功能.
安装 CMake
使用包管理器安装 CMake
-
Ubuntu/Debian:
sudo apt update sudo apt install cmake
-
CentOS/RHEL:
sudo yum install epel-release sudo yum install cmake
-
macOS:
brew install cmake
-
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
这里:
./bootstrap
用于初始化构建环境.make
进行源码编译.sudo make install
将构建的 CMake 安装到系统.
确认安装成功
在命令行执行以下命令:
cmake --version
应显示安装的版本信息:
cmake version 3.xx.x
如果显示最新版本, 则说明安装成功.
通过连续升级和选择适合版本, 从源码构建可以确保项目最大限度地适配项目构建需求.
样例一: 构建 Hello World
1. 创建项目文件
创建一个目录demo
, 包含如下文件:
demo/
├── 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(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 生成何种项目, 可以是下面之一:
Unix Makefiles
(Linux 默认的生成工具)Ninja
: 跨平台构建工具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 版本
在编译项目的时候通常我们会编译两个版本:
- Debug 版本: 适合调试, 测试, 保留符合(代码行数)
- 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
:
-
编译 Debug 版本
cmake -DCMAKE_BUILD_TYPE=Debug ..
-
编译 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
命令来执行所有测试样例.
- 测试文件
test.cpp
:
#include <gtest/gtest.h>
// 示例测试用例
TEST(SampleTest, Addition) { EXPECT_EQ(1 + 1, 2); }
TEST(SampleTest, Multiplication) { EXPECT_EQ(2 * 3, 6); }
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 将帮助开发者显著提升构建效率.