使用 Modern CMake 构建现代 C++ 项目:target从入门到实践
CMake 是 C++ 项目中最主流的构建系统之一,而随着 CMake 3.x ,4.x的发展,Modern CMake(现代 CMake)已成为推荐的最佳实践。
本篇文章将用通俗易懂的方式,带你从头理解并实践 Modern CMake 的核心理念,包括 target_*
系列命令、文件组织、安装与打包、测试下游项目、以及 CMakePresets 的使用。
一、Modern CMake 的核心思想
传统 CMake 中常用的 include_directories()
、add_definitions()
、file(GLOB ...)
等方式,容易带来依赖混乱。
Modern CMake 提倡:一切都通过 target 管理。
核心原则是:
- 每个库/程序用一个
add_library()
或add_executable()
创建 target - 使用
target_sources()
、target_include_directories()
、target_link_libraries()
配置 target 的行为 - 避免全局变量,改用 target 作用域
二、目录结构设计
一个清晰的目录结构是 Modern CMake 的第一步:
foo_project/
├── CMakeLists.txt
├── include/
│ └── foo/
│ └── foo.h
├── src/
│ └── foo.cpp
├── test_app/
│ ├── main.cpp
│ └── CMakeLists.txt
├── CMakePresets.json
└── fooConfig.cmake.in
include/foo/foo.h
:公共头文件src/foo.cpp
:实现文件test_app/
:测试程序
三、使用 target_sources 管理源码
Modern CMake 中推荐使用 target_sources()
明确地将头文件和源文件加入库:
add_library(foo) # 创建目标 footarget_sources(fooPUBLICFILE_SET foo_headers TYPE HEADERS # 定义一个文件集用于安装头文件BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/includeFILES ${CMAKE_CURRENT_SOURCE_DIR}/include/foo/foo.hPRIVATE${CMAKE_CURRENT_SOURCE_DIR}/src/foo.cpp # 私有源文件不被安装
)
这不仅清晰地标明了头文件和源文件的作用域,还能与 install()
一起自动安装。
四、安装与导出库
使用 install()
命令,可以将构建好的库、头文件和 CMake 配置文件安装到系统或本地目录中。
install(TARGETS fooEXPORT fooTargets # 导出 target 定义供下游使用FILE_SET foo_headers DESTINATION include # 安装头文件到 include/LIBRARY DESTINATION lib # 安装 .so 动态库ARCHIVE DESTINATION lib # 安装 .a 静态库
)install(EXPORT fooTargets # 安装导出文件FILE fooTargets.cmakeNAMESPACE foo:: # 添加命名空间,使用 foo::fooDESTINATION lib/cmake/foo
)configure_package_config_file(fooConfig.cmake.in${CMAKE_CURRENT_BINARY_DIR}/fooConfig.cmakeINSTALL_DESTINATION lib/cmake/foo
)install(FILES${CMAKE_CURRENT_BINARY_DIR}/fooConfig.cmakeDESTINATION lib/cmake/foo
)
这样,其他项目就可以使用 find_package(foo)
来链接你的库了。
五、CMakePresets.json 的使用
CMakePresets.json
是 CMake 3.19+ 引入的新功能,可以统一预设构建参数:
{"version": 3,"configurePresets": [{"name": "release","generator": "Unix Makefiles","binaryDir": "${sourceDir}/build","cacheVariables": {"CMAKE_BUILD_TYPE": "Release","CMAKE_INSTALL_PREFIX": "${sourceDir}/install"}}],"buildPresets": [{ "name": "release", "configurePreset": "release" }]
}
使用方式非常简单:
cmake --preset=release # 配置构建目录
cmake --build --preset=release # 构建项目
cmake --install build # 安装到 install/ 目录
实际终端输出如下所示:
$ cmake --preset=release
-- The CXX compiler identification is GNU 10.2.1
-- 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
-- Generating done
-- Build files have been written to: /.../foo_project/build$ cmake --build --preset=release
[ 50%] Building CXX object CMakeFiles/foo.dir/src/foo.cpp.o
[100%] Linking CXX static library libfoo.a
[100%] Built target foo$ cmake --install build
-- Install configuration: "Release"
-- Installing: /.../foo_project/install/lib/libfoo.a
-- Installing: /.../foo_project/install/include/foo/foo.h
-- Installing: /.../foo_project/install/lib/cmake/foo/fooTargets.cmake
-- Installing: /.../foo_project/install/lib/cmake/foo/fooTargets-release.cmake
-- Installing: /.../foo_project/install/lib/cmake/foo/fooConfig.cmake
六、测试下游项目
在项目根目录下我们还设置了一个 test_app/
目录,用于模拟一个使用 foo
库的下游项目。
// test_app/main.cpp
#include <foo/foo.h>int main() {foo::say_hello();return 0;
}
# test_app/CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(test_app)find_package(foo REQUIRED PATHS ../install/lib/cmake/foo) # 查找安装后的包add_executable(test_app main.cpp)
target_link_libraries(test_app PRIVATE foo::foo) # 链接目标
构建和运行:
$ cmake -B build -DCMAKE_PREFIX_PATH=../install
-- The CXX compiler identification is GNU 10.2.1
-- 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
-- Generating done
-- Build files have been written to: /.../test_app/build$ cmake --build build
[ 50%] Building CXX object CMakeFiles/test_app.dir/main.cpp.o
[100%] Linking CXX executable test_app
[100%] Built target test_app$ ./build/test_app
Hello from foo!
说明 foo
库已经成功安装并被下游项目引用。
七、安装生成的配置文件说明
安装完成后你会得到:
install/lib/cmake/foo/
├── fooConfig.cmake
├── fooTargets.cmake
└── fooTargets-release.cmake
这些文件让其他项目通过 find_package(foo REQUIRED PATHS ./install/lib/cmake/foo)
使用 foo::foo
目标,无需写死路径。
它们的作用分别是:
文件 | 作用 |
---|---|
fooConfig.cmake | 包配置入口文件,供 find_package() 加载 |
fooTargets.cmake | 定义导出的目标及其依赖 |
fooTargets-release.cmake | 定义 release 模式下库文件路径 |
八、总结
功能 | Modern CMake 命令 | 说明 |
---|---|---|
添加源码 | add_library() + target_sources() | 创建目标并声明源文件和头文件 |
设置包含路径 | target_include_directories() | 设置头文件查找路径 |
链接库 | target_link_libraries() | 连接依赖库 |
安装头文件 | install(FILE_SET ...) | 安装头文件并保持目录结构 |
导出配置 | install(EXPORT ...) | 安装 target 定义供 find_package() 使用 |
包配置文件 | configure_package_config_file() | 生成并安装 Config.cmake 供下游使用 |
使用 preset 构建 | cmake --preset=... | 快速构建统一配置 |
测试下游项目 | find_package() + target_link | 验证库是否被成功引用 |
通过 Modern CMake,构建脚本更可维护、更易复用、更标准化。
下一步,你可以尝试将自己的项目改造成 Modern CMake 风格,或基于本文的结构构建可复用的 C++ SDK。
📌 如需完整示例项目打包文件、测试程序或跨平台支持配置,欢迎留言交流!