30、实战案例:从零构建一个完整项目的 Presets 配置体系

好了,前面讲了那么多理论,咱们终于到了动手环节。这一章,我会带着你从零开始,为一个真实项目搭建完整的 Presets 配置体系。说白了,就是把前面那些零散的知识点串起来,搞一个能直接用的东西。

我个人习惯,每次接手新项目,第一件事就是搭构建环境。为什么?因为后面几十号人、几百次编译,全靠这个底子。你想想看,如果基础没打好,后面每个人都在自己的机器上瞎调,那不乱套了?

项目背景

假设我们有一个跨平台 C++ 项目,叫 DataPipeline。它是一个数据处理引擎,需要支持:

  • Windows(VS2022 + MSVC)
  • Linux(GCC 11 + Clang 14)
  • macOS(Apple Clang)

项目结构大概这样:

DataPipeline/
├── CMakeLists.txt
├── src/
├── include/
├── tests/
├── tools/
└── CMakePresets.json

第一步:定义基础 Presets

先别急着写复杂的配置。我建议从最基础的开始——定义 configurePresets。这一步决定了你的项目能在哪些编译器上跑。

{
  "version": 6,
  "configurePresets": [
    {
      "name": "default",
      "hidden": true,
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/${presetName}",
      "cacheVariables": {
        "CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
        "CMAKE_CXX_STANDARD": "20",
        "CMAKE_CXX_STANDARD_REQUIRED": "ON"
      }
    },
    {
      "name": "windows-msvc",
      "inherits": "default",
      "description": "Windows 上用 MSVC 编译",
      "generator": "Visual Studio 17 2022",
      "architecture": "x64",
      "cacheVariables": {
        "CMAKE_C_COMPILER": "cl",
        "CMAKE_CXX_COMPILER": "cl"
      }
    },
    {
      "name": "linux-gcc",
      "inherits": "default",
      "description": "Linux 上用 GCC 11 编译",
      "cacheVariables": {
        "CMAKE_C_COMPILER": "gcc-11",
        "CMAKE_CXX_COMPILER": "g++-11"
      }
    },
    {
      "name": "macos-clang",
      "inherits": "default",
      "description": "macOS 上用 Apple Clang 编译",
      "cacheVariables": {
        "CMAKE_C_COMPILER": "clang",
        "CMAKE_CXX_COMPILER": "clang++"
      }
    }
  ]
}
我的经验:这里把 default 设为 hidden: true,是个好习惯。它只作为基类,不直接暴露给用户。这样你后面加新配置时,只需要继承它,不用重复写那些公共变量。

第二步:添加构建 Presets

配置写好了,接下来是 buildPresets。这一步决定了怎么编译——Debug 还是 Release?要不要跑测试?

{
  "buildPresets": [
    {
      "name": "debug",
      "configurePreset": "default",
      "description": "Debug 构建,带调试符号",
      "configuration": "Debug",
      "jobs": 8
    },
    {
      "name": "release",
      "configurePreset": "default",
      "description": "Release 构建,优化全开",
      "configuration": "Release",
      "jobs": 8
    },
    {
      "name": "release-with-tests",
      "configurePreset": "default",
      "description": "Release 构建 + 编译测试",
      "configuration": "Release",
      "targets": ["all", "tests"],
      "jobs": 8
    }
  ]
}

嗯,这里要注意:jobs 这个参数,我建议根据你团队的机器配置来设。我曾经在一个项目里设了 jobs: 16,结果有同事的笔记本只有 4 核,直接卡死。后来我改成 jobs: 0(让 CMake 自动检测),反而更稳。

第三步:测试 Presets 与打包

测试 Presets 其实很简单,就是告诉 CTest 怎么跑。但这里有个坑——不同平台的测试命令可能不一样。

{
  "testPresets": [
    {
      "name": "unit-tests",
      "configurePreset": "default",
      "description": "运行单元测试",
      "configuration": "Debug",
      "output": {
        "outputOnFailure": true,
        "verbosity": "default"
      },
      "execution": {
        "noTestsAction": "error",
        "stopOnFailure": false
      }
    },
    {
      "name": "integration-tests",
      "configurePreset": "default",
      "description": "运行集成测试(耗时较长)",
      "configuration": "Release",
      "output": {
        "outputOnFailure": true,
        "verbosity": "verbose"
      },
      "execution": {
        "timeout": 600,
        "stopOnFailure": true
      }
    }
  ]
}
避坑指南:我曾经在集成测试里忘了设 timeout,结果有个测试卡在死循环里,CI 跑了整整 8 小时才超时。所以,对于耗时较长的测试,一定要显式设置超时时间。

第四步:条件化配置(平台差异处理)

不同平台有不同的需求。比如 Windows 上需要安装 OpenSSL,Linux 上需要 pthread。这时候 conditions 就派上用场了。

{
  "configurePresets": [
    {
      "name": "windows-msvc-debug",
      "inherits": "windows-msvc",
      "description": "Windows Debug 构建",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug",
        "USE_OPENSSL": "ON"
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Windows"
      }
    },
    {
      "name": "linux-gcc-release",
      "inherits": "linux-gcc",
      "description": "Linux Release 构建",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "USE_PTHREAD": "ON"
      },
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Linux"
      }
    }
  ]
}

你想想看,如果没有条件化配置,你每次在不同平台上都得手动指定变量,多麻烦。有了 condition,CMake 会自动判断当前系统,只加载匹配的配置。

第五步:完整配置体系总览

下面这张图,是我画的一个完整配置体系的结构图。它展示了从基础配置到平台特化、再到构建和测试的完整链路。

DataPipeline Presets 配置体系 基础配置 (default) Windows MSVC Linux GCC macOS Clang Debug / Release Debug / Release Debug / Release 单元测试 (unit-tests) 集成测试 (integration-tests) 条件化配置:Windows → OpenSSL | Linux → pthread | macOS → 无额外依赖 通过 condition 字段自动匹配平台,无需手动切换

第六步:团队共享与 CI/CD 集成

配置写好了,怎么让团队用起来?很简单,把 CMakePresets.json 提交到 Git 仓库里。然后每个人只需要:

# 配置
cmake --preset linux-gcc-release

# 构建
cmake --build --preset release

# 测试
ctest --preset unit-tests

在 CI 里也一样。比如 GitHub Actions 的配置:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Configure
        run: cmake --preset linux-gcc-release
      - name: Build
        run: cmake --build --preset release
      - name: Test
        run: ctest --preset unit-tests
核心要点:Presets 配置体系的核心价值,就是让「配置」这件事变得可重复、可共享、可自动化。你只需要维护一份 JSON 文件,所有平台、所有构建类型、所有测试场景,全都在里面。

常见问题与避坑

  • 版本兼容性:CMake 3.20 以下不支持 Presets。我建议团队统一升级到 3.23+,避免踩坑。
  • 路径问题:binaryDir 里用 ${presetName} 是个好习惯。这样每个配置都有自己的构建目录,互不干扰。
  • 环境变量:如果项目依赖第三方库路径,建议在 environment 块里设置,而不是写死在 CMakeLists.txt 里。
  • 不要过度嵌套:我见过有人搞了 5 层继承,最后自己都搞不清哪个变量从哪来的。建议最多 2-3 层。

好了,这一章的内容就到这里。你跟着我一步步走下来,应该已经能搭建一个完整的 Presets 配置体系了。记住,好的配置体系,能让团队省下大量「环境配置」的时间,把精力真正放在代码上。