Skip to Main Content
CMake .cmake 模块与 Preset:可复现构建的拆分技巧Back to Top

CMake .cmake 模块与 Preset:可复现构建的拆分技巧

3 minutes

.cmake 模块与 Preset 使用技巧

根目录 CMakeLists.txt 一旦堆满 find_package、警告选项、生成器判定,项目会很快「能编但难讲」。更稳的做法是:

  1. 按职责拆 .cmake 模块(工具链 / 生成器 / 警告 / 日志 / 安装)
  2. CMakePresets.json 固定常用组合(本机 Ninja、CI Release、开 tidy 等)

本文是实操向清单,偏 CMake 3.20+ / 4.x。


1. 为什么要拆 .cmake

问题拆模块后
新人不知道改哪README 写「改 cmake/ToolchainClang.cmake
AppleClang / Homebrew LLVM 混用工具链探测集中一处
生成器搞错(modules 需要 Ninja)Generator.cmake 统一 FATAL
选项满天飞preset 命名表达意图

建议分层(名字可自定,职责别混):

cmake/
  Generator.cmake          # 强制/探测生成器
  ToolchainClang.cmake     # 编译器、sysroot、modules JSON
  StdlibConfig.cmake       # import std / stdlib 探测
  CompilerWarnings.cmake   # -Wall 与可选 tidy
  BuildLog.cmake           # 彩色日志 / tee
  ProjectSetup.cmake       # 打印关键变量摘要
  Modules.cmake            # 挂 CXX modules 相关

CMakeLists.txt 只做:project()add_subdirectory / add_executable、按顺序 include()


2. .cmake 编写技巧

2.1 用 option() / CACHE 暴露开关,默认保守

option(CXX_ENABLE_CLANG_TIDY "Enable clang-tidy" OFF)
option(CXX_ENABLE_GTEST "Build tests" OFF)
set(CXX_PREFERRED_COMPILER "clang++" CACHE STRING "clang++|g++")

默认 OFF,避免「拉下来编不过」。

2.2 致命条件用清晰单行字符串

CMake 的 list 与多参数 set(msg "a" "b") 容易把 FATAL_ERROR 显示成空白。拼错误信息时用 string(APPEND)

set(_err "")
string(APPEND _err "Ninja is required for C++23 modules.\n")
string(APPEND _err "Hint: export CMAKE_GENERATOR=Ninja or use -G Ninja")
message(FATAL_ERROR "${_err}")

2.3 生成器:早失败

if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
  message(FATAL_ERROR "Use Ninja (C++ modules / import std). Got: ${CMAKE_GENERATOR}")
endif()

配合根目录 .envrcexport CMAKE_GENERATOR=Ninja,减少「裸 cmake .. 掉进 Unix Makefiles」的缓存坑。

2.4 include 顺序要稳定

典型顺序:

include(cmake/Generator.cmake)
include(cmake/ToolchainClang.cmake)
include(cmake/StdlibConfig.cmake)
# … project() 之后再:
include(cmake/CompilerWarnings.cmake)
include(cmake/ProjectSetup.cmake)

工具链相关尽量在 project() 前完成;依赖 PROJECT_* 的摘要放后面。


3. CMakePresets.json 技巧

3.1 最小结构

{
  "version": 6,
  "configurePresets": [
    {
      "name": "dev",
      "displayName": "Dev (Ninja RelWithDebInfo)",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "RelWithDebInfo",
        "CXX_PREFERRED_COMPILER": "clang++",
        "CXX_ENABLE_CLANG_TIDY": "OFF"
      }
    },
    {
      "name": "tidy",
      "inherits": "dev",
      "displayName": "Dev + clang-tidy",
      "cacheVariables": {
        "CXX_ENABLE_CLANG_TIDY": "ON"
      }
    }
  ],
  "buildPresets": [
    { "name": "dev", "configurePreset": "dev" },
    { "name": "tidy", "configurePreset": "tidy" }
  ]
}

3.2 实用习惯

技巧说明
inherits共用 generator / binaryDir,只覆盖差异
binaryDir 固定 build/与文档「进 build 再 cmake」一致
显式写 generator: Ninja不依赖环境变量也正确
分开 dev / ci / tidy意图可读,少口头约定
勿把本机绝对路径写进 preset${sourceDir}、环境变量

3.3 命令

cmake --preset dev
cmake --build --preset dev

cmake --preset tidy
cmake --build --preset tidy

无 preset 时等价习惯:

mkdir -p build && cd build
cmake -G Ninja ..
cmake --build .

4. 和 clang-tidy 的组合

这样「模块拆分」负责机制,「preset」负责场景,两边都不膨胀。


5. 排障速查

现象处理
cmake .. 后生成器是 Makefilesrm -rf build && mkdir build,再用 Ninja / preset
FATAL 信息空白检查是否把错误字符串写成了 CMake list
tidy 找不到PATH 含 Homebrew LLVM;或 find_program 失败时明确 WARNING
preset 不生效确认 CMakePresets.json 在源码根;CMake ≥ 3.19(建议 3.23+ / 4.x)

6. 小结

把「怎么探测环境」放进 .cmake,把「这次我想怎样编」放进 preset。根 CMakeLists.txt 保持薄,专栏工程和脚手架模板就能共用同一套心智模型。