# 现代 CMake 使用指南
CMake 是一个开源的、跨平台的自动化构建系统,用于管理软件项目的构建过程。它最初设计用于简化编写外部构建文件的过程,以适应不同的编译器和操作系统。CMake 使用简单的配置文件(通常称为 `CMakeLists.txt`)来描述项目结构、依赖关系、编译选项和其他构建相关的设置,然后根据这些配置自动生成适合特定平台(如 Unix Makefiles、Microsoft Visual Studio、Xcode 等)的构建文件。
### 主要特点:
1. **跨平台性**:CMake 支持多种操作系统和编译器,包括 Windows、Linux、macOS、Android、iOS 以及 GCC、Clang、MSVC 等编译器,使得开发者能以统一的方式在不同平台上构建项目。
2. **简洁的语法**:CMake 提供了一套高级的脚本语言,允许用户以相对简洁的命令描述构建逻辑,避免了手动编写复杂的 Makefile 或 IDE 项目文件。
3. **模块化**:项目可以通过 `add_subdirectory` 命令包含子目录,每个子目录可以有自己的 `CMakeLists.txt` 文件,这样可以实现模块化管理和复用构建逻辑。
4. **编译选项**:CMake 支持通过命令行参数或 GUI 工具动态修改构建配置,比如选择构建类型(Debug、Release)、启用或禁用特定功能、指定安装路径等。
5. **库管理**:CMake 支持静态库、动态库的创建与链接,并通过 `target_link_libraries`、`target_include_directories` 等命令管理依赖关系。特别是 `INTERFACE`、`PUBLIC`、`PRIVATE` 属性的使用,使得库的使用者能清晰地了解如何正确链接和使用库。
6. **测试与安装**:CMake 可以集成单元测试(如 Google Test),并通过 `ctest` 命令执行测试。同时,`install` 命令支持定义安装规则,方便将构建结果部署到指定位置。
7. **代码生成**:CMake 可以通过 `configure_file` 命令基于模板生成源代码或配置头文件,方便传递编译时的宏定义或版本信息。
8. **现代CMake实践**:现代CMake强调使用更清晰、更面向对象的风格,减少全局变量的使用,更多地依赖于目标属性和接口库来控制编译和链接行为,这有助于提高代码的可维护性和可读性。
总之,CMake极大地简化了跨平台软件项目的构建过程,提高了开发者的生产力,是现代C++项目开发不可或缺的工具之一。
本文全面介绍现代 CMake 的实践技巧与最佳实践,旨在帮助开发者高效管理项目构建过程。展示了多种使用CMake的方式,包括快速设置构建目录、编译不同配置以及安装和测试项目,同时提及了如何在Visual Studio Code中利用插件无缝集成CMake工作流,提升开发效率。读者不仅能掌握CMake的基本操作,还能深入理解其高级功能,从而在实际项目中运用现代CMake实践,构建高质量的软件项目。
## 命令行方式使用 cmake
```sh
# cmake [<options>] -S <path-to-source> -B <path-to-build>
# 方式1:在源码目录执行
cmake -S . -B ./build
# 方式2:进入编译目录执行
cd build
cmake ..
cmake --build . --config Release
# 方式3:创建两个子目录分别编译
cd debug
cmake --build . --config Debug
cd ../release
cmake --build . --config Release
# 安装
cmake --install . --prefix "D:/path-to-install"
# 测试,./build 目录中执行
ctest -vv -C Release # ctest 跟随 cmake 安装
# 打包成安装包
cpack -C Release
```
## vscode 插件方式使用cmake
点击状态栏按钮可快速编译、调试、运行

安装 vscode 插件: "twxs.cmake", "ms-vscode.cmake-tools"
设置使用的 cmake 路径

指定源码根目录:

## cmake 指南
基于官网教程:[CMake Tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html)
含可执行程序、链接库编译,自动测试及英语打包的核心 cmake 功能示例
## 项目设置
```py
# 指定 cmake 最低版本,以保证兼容性
cmake_minimum_required(VERSION 3.15)
# 设置项目名称及版本
project(Tutorial VERSION 1.0 LANGUAGES CXX)
# 打印信息
message(STATUS "PROJECT_BINARY_DIR: " ${PROJECT_BINARY_DIR})
# 运行时在哪里查找动态库
if(APPLE)
set(CMAKE_INSTALL_RPATH "@executable_path/../lib")
elseif(UNIX)
set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib")
endif()
# 表达式生成器 $<...>,如
# target_include_directories(tgt PRIVATE /opt/include/$<CXX_COMPILER_ID>)
# 根据所使用的c++编译器生成 /opt/include/GNU, /opt/include/Clang等。
# 它们支持条件链接、编译时使用的条件定义、条件包含目录等等。
# 条件可能基于构建配置、目标属性、平台信息或任何其他可查询信息。
# 文档:https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#introduction
```
### 编译可执行程序
```py
# 生成的可执行文件带d,适用于单配置生成器如(gcc),不适用于多配置生成器(例如Visual Studio)
set(CMAKE_DEBUG_POSTFIX d)
# 通过指定 INTERFACE 作用域创建接口库,目标都链接该接口库,以实现通过唯一源设置编译参数
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
# add compiler warning flags just when building this project via the BUILD_INTERFACE genex
# BUILD_INTERFACE 指定仅在编译项目时添加编译器警告
set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU,LCC>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
"$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)
# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
# 默认编译为动态库, cmake .. -DBUILD_SHARED_LIBS=OFF 编译为静态库
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
# 从 .in 生成头文件,传递项目版本号
# #define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
configure_file(TutorialConfig.h.in TutorialConfig.h)
# 添加 cmake 子目录, 目录中需含有 CMakeLists.txt,会继承父级变量
add_subdirectory(MathFunctions)
# add the executable
add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
# 添加库的现代cmake方式: 因为 MathFunctions library define its own usage requirements, 仅指定要链接该库就行了
# PUBLIC 作用域之后的库被链接到目标,并成为对外链接库接口的一部分。
# PRIVATE 后面的库和目标被链接到目标,但不作为对外链接库接口的一部分。
# INTERFACE 后面的库被附加到链接接口,不链接当前<目标>。
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)
# 添加头文件路径,因为前面生成的 TutorialConfig.h 在项目编译目录中,需要指定该目录以找到该文件
target_include_directories(Tutorial PUBLIC
"${PROJECT_BINARY_DIR}"
)
```
### 编译库
```sh
# add the library that runs
add_library(MathFunctions MathFunctions.cxx)
# let our library define its own usage requirements
# 添加 INTERFACE 作用域声明任何使用该库的项目都包含当前源码路径,但该库不需要依赖该头文件
# 说明:
# 如果头文件只被库的实现使用,�