ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Windows下CMake 3.25免安装包:版本锁定、预设与排错

Windows下CMake 3.25免安装包:版本锁定、预设与排错

简介:面向 Windows 平台 C++ 开发者与构建工程师的 CMake 3.25 离线安装包,适合需要在 Visual Studio、MinGW 等编译器环境下配置、生成和迁移工程的技术人员使用。压缩包内共 7090 个文件,约 36.31MB,以 txt、html、rst 文档和 cmake 模块脚本为主体,辅以少量 exe 安装程序、json 配置、png 图示及 c、cxx、cpp 示例源码,既包含可执行工具,也保留较完整的命令、属性、变量与策略说明文档。已有 1337 人学习下载,说明其在版本升级和本地环境搭建场景中具有一定参考价值。对于需要查阅 cmake、ctest、cpack、cmake-gui 等命令手册,或希望理解 CMakeLists 编写、缓存管理、编译器适配与构建排错的读者,可通过包内文档快速定位官方说明;安装后亦可在命令行或图形界面中完成源码目录、构建目录和编译器选项设置,减少从零摸索成本。

1. 为什么我还在用 CMake 3.25 的 Windows 包:一次装错版本的连环翻车

上周有个读者把工程丢过来,说 cmake c++ 这套流程在他机器上死活配不起来,日志刷了一屏关于cmake_minimum_required版本过低的弃用告警,而他本地cmake --version打出来是 3.31。这类问题九成不是代码写错,是机器上并存着三四个 CMake 实例,PATH 里第一个不是你以为的那个。CMake 3.25 的 Windows 免安装包就是为这种场景准备的:解压即用、不写注册表、不抢系统目录,一个工程一个版本,切换成本接近零。它适合要同时维护几套老工程、又不想每台机器都跑安装程序的人;也适合想把构建链固定在某个版本、避免换台机器就编译不过的团队。下面按我实际拆包、搭环境的顺序讲。

2. 拆开这个压缩包:bin、share、doc 三块分别管什么

拿到的是一个.zip,不是.msi。这决定了后面所有操作都围绕「解压到哪、怎么让它被找到」展开,而不是「下一步、下一步、完成」。免安装包的本质是把一整套 CMake 运行时和模块库塞进一个目录树,任何路径下都能跑,删掉目录就等于卸载干净——这一点在需要来回切版本的机器上价值很大。

2.1 目录树里真正会被用到的三个位置

解压之后先别急着配 PATH,把目录看一遍。顶层一般有bin、doc、man、share四个目录,对日常构建来说,前三个之外的东西才是重点。

路径内容什么时候会用到
bin/cmake.exe主程序,配置与命令行入口所有cmake -S/-B/--build/--install操作
bin/ctest.exe测试驱动跑add_test注册的用例
bin/cpack.exe打包驱动生成安装包或归档
share/cmake-3.25/ModulesFindXXX.cmake、平台与编译器探测脚本find_package找不到包时,先来这里确认有没有对应模块
share/cmake-3.25/Templates新建工程的模板文件基本不用手动碰
doc/cmake-3.25离线手册断网环境查命令签名

Modules目录是最容易被低估的一块。很多「find_package报 Could not find」的问题,根因是模块名拼错或者这个版本压根没带对应模块,而不是库没装。我习惯在排查前先Get-ChildItem share\cmake-3.25\Modules\Find*.cmake | Select-String 关键词过一遍,确认模块存在再往下查路径。

解压本身有两个硬约束:路径不要带中文,不要带空格。不是说一定崩,而是带空格时某些老项目的自定义命令、外部脚本拼命令行会少引号,报错信息还特别绕,排查成本远高于换个目录。

# 解压到短路径,避免后续脚本拼命令行时被空格截断 Expand-Archive -Path .\cmake-3.25.x-windows-x86_64.zip -DestinationPath D:\tools\ # 确认可执行文件确实落在 bin 下,而不是多套了一层目录 Get-ChildItem D:\tools\cmake-3.25.x-windows-x86_64\bin -Filter *.exe

Expand-Archive在 Windows PowerShell 5.1 上对大压缩包偏慢,几百 MB 的包建议直接用资源管理器右键解压,行为一致但快不少。解压完要确认的是bin\cmake.exe的直接父目录是不是bin,因为有些第三方重新打包的版本会多嵌一层同名目录,导致后面配 PATH 全错。

2.2 版本锁定的理由:3.25 这条线的能力边界

版本不是越新越好,这是踩过才知道的。3.25 属于一个「够用且稳定」的中间点:它已经带上了CMakePresets.json的 schema 6、block()/endblock()命令、add_subdirectory(... SYSTEM)、SYSTEM目标属性,以及 workflow presets;同时又没有后续版本里那些会改变默认行为的动作。对一套需要长期维护的工程来说,这种「能力齐、行为稳」的组合比追新更有价值。

具体到日常会用到的几个点:

  • Presets schema 6:支持configurePresets、buildPresets、testPresets、packagePresets、workflowPresets五类,团队里可以把生成器、架构、缓存变量、构建类型全部写进文件,新人拉下来直接cmake --preset就行,不用口头传参数。
  • block()/endblock():给变量加作用域。以前写宏或者一段临时改CMAKE_CXX_FLAGS的逻辑,改完得手动还原,忘了就污染后续所有目标;现在用block()包起来,出块自动恢复。
  • add_subdirectory(... SYSTEM):把第三方子目录当系统头处理,编译器不再对它里面的头文件报警告。接老库时这个能省掉一大堆「第三方头文件警告淹没自己的警告」的噪音。
  • 最低版本策略:3.25 对cmake_minimum_required写太低的工程会给弃用告警。这类告警本身无害,但如果工程开了-Werror或者把告警当错误处理,就会直接失败。老工程迁过来第一件事就是把cmake_minimum_required抬到 3.25 或至少 3.10 以上。

反过来说,如果你的工程依赖某个只有更高版本才有的命令,硬钉 3.25 会得到一句Unknown CMake command,这个报错很直白,不会误导。真正麻烦的是相反的情况:用了新版本才有的默认行为,降回 3.25 后不报错但结果不对,所以版本要么全组统一,要么在预设里写死,别靠个人机器上的默认值。

2.3 让cmake --version只有一个答案

PATH 冲突是 Windows 上最高频的翻车点,没有之一。典型症状是命令行里cmake --version显示 3.25,但某个 IDE 内部构建时报的命令不认识,或者反过来。原因是不同进程继承的 PATH 不一样,加上安装版会往系统目录和注册表写东西,优先级很难靠肉眼判断。

第一步永远是查全部候选,而不是查第一个:

# 列出 PATH 中所有名为 cmake 的可执行文件,顺序即优先级 where.exe cmake # 只在当前会话里前置,先验证再决定要不要固化 $env:Path = "D:\tools\cmake-3.25.x-windows-x86_64\bin;" + $env:Path cmake --version

where.exe会按 PATH 顺序把所有匹配项都列出来,看到两条以上就说明有冲突,得先清理。$env:Path用前置而不是追加,是为了让新版本压过旧版本;只改当前会话的好处是关掉窗口就恢复,试错零成本。验证通过之后再写进用户级环境变量,别一上来就动系统级——系统级改坏了影响所有账号,而且很多安装程序会往里插自己的路径,你的修改过段时间就被挤到后面去了。

更稳的做法是干脆不依赖 PATH。工程根目录放一份预设文件,里面用绝对路径调用,或者在 CI 脚本里用完整路径D:\tools\...\bin\cmake.exe。IDE 里对应的是cmake.cmakePath这类配置项,指到具体文件而不是让它去 PATH 里找。多版本共存的机器上,我一般会保留 PATH 里一个「默认版本」,其余全部通过绝对路径或预设调用,这样出问题时至少有一个已知的参照点。

3. 从空目录到可执行文件:一份最小 CMake 工程的四个动作

先跑通一个最小工程,再往上加东西。跳过这一步直接上真实项目,一旦报错就分不清是环境问题还是工程配置问题。这一章的四步——写CMakeLists.txt、配置、构建、安装——每一步都有独立的失败模式,拆开验证最省时间。

3.1 三行起步,再逐层加料

最小可用的CMakeLists.txt只要三行。第一行声明最低版本,第二行声明工程名和支持的语言,第三行声明目标。

# 最低版本必须写,且不要低于 3.10;写 3.25 表示这份工程按 3.25 的行为解析 cmake_minimum_required(VERSION 3.25) # LANGUAGES CXX 明确只启用 C++,避免在没装 C 编译器的环境里误报 project(hello_cmake LANGUAGES CXX) # 可执行目标名 hello,源文件与 CMakeLists.txt 同目录 add_executable(hello main.cpp) # 语言标准用 target_compile_features 表达,比全局 CMAKE_CXX_STANDARD 更精准 target_compile_features(hello PRIVATE cxx_std_17)

这四行里最值得说的是最后一行。全局设CMAKE_CXX_STANDARD是常见写法,但它会影响所有目标,包括第三方子目录;用target_compile_features挂在具体目标上,作用域清晰,而且当编译器不支持时会在配置阶段直接报错,而不是等到编译阶段才抛一堆语法错误。

对应的main.cpp写个最小可运行的:

#include <iostream> int main() { std::cout << "hello from cmake 3.25\n"; return 0; }

接着往上加库和包含目录。把业务代码拆成一个静态库、可执行文件只留main,这是后续接依赖、写测试的基础结构:

add_library(greeter STATIC src/greeter.cpp) # PUBLIC 表示使用者自动继承这个包含目录,PRIVATE 则不外传 target_include_directories(greeter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) add_executable(hello main.cpp) target_link_libraries(hello PRIVATE greeter)

PUBLIC和PRIVATE的区别是这套机制的核心:头文件目录对使用者可见就用PUBLIC,只在库内部用就PRIVATE。写反了不会立刻报错,但依赖传播链会出错——下游目标编译时找不到头文件,或者反过来把内部目录暴露出去污染别人的包含路径。这种错误在工程规模变大后极难定位,一开始就写对最省事。

3.2 生成器怎么选:VS 2022、Ninja 和它们的适用面

生成器决定了 CMake 最终产出什么。Windows 上主要是两个方向:Visual Studio 生成器,产出.sln/.vcxproj;Ninja 生成器,产出一个build.ninja。

生成器典型命令参数优点代价
Visual Studio 17 2022-G "Visual Studio 17 2022" -A x64自动定位 MSVC,不用配环境,能直接开 IDE 调试配置慢,产物目录结构深
Ninja-G Ninja -DCMAKE_BUILD_TYPE=Release增量构建快,命令行干净需要cl.exe已在环境里,否则找不到编译器
NMake Makefiles-G "NMake Makefiles"兼容老环境并行支持弱,基本不推荐

选择依据不是「哪个先进」,而是「谁来构建」。如果日常是在 VS 里按 F5 调试,就用 VS 生成器,省掉配编译器的所有麻烦;如果是脚本化、CI、或者每天要跑几十次构建,Ninja 的增量速度优势很明显。我通常两套预设都留着,调试用 VS,跑验证用 Ninja,产物目录分开,互不干扰。

Ninja 的第一个坑几乎人人都会遇到:直接在普通 PowerShell 里cmake -G Ninja,配置阶段报找不到cl。原因是 Ninja 不做编译器发现,它要求编译器已经在环境中。解决办法是在 VS 的开发者命令行里执行,或者先把环境变量拉进来:

# 加载 MSVC 环境变量,路径按实际 VS 安装位置调整 & "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1" -Arch amd64 # 验证编译器可见 cl cmake -S . -B build/ninja -G Ninja -DCMAKE_BUILD_TYPE=Release

Launch-VsDevShell.ps1只影响当前 PowerShell 会话,不会污染系统环境,比手动拼INCLUDE/LIB可靠得多。执行完再cl一下,能打出版本号说明环境就绪;打不出来就是路径猜错了,去 VS 安装目录下找VsDevCmd.bat那一套。

3.3 配置、构建、安装:三条命令各自的参数

从 3.13 起支持-S/-B,源码目录和构建目录彻底分离。这个习惯要养成:构建目录是可以整个删掉重建的,源码目录永远不产生中间文件。

# 配置:生成器、架构、缓存变量都在这一步定下来 cmake -S . -B build/vs2022 -G "Visual Studio 17 2022" -A x64 # 构建:多配置生成器必须显式给 --config,不给就是 Debug cmake --build build/vs2022 --config Release --parallel # 安装:--prefix 决定落地位置,同样要跟 --config cmake --install build/vs2022 --config Release --prefix D:/dist/hello

三条命令里最容易出问题的是--config。VS 这类多配置生成器在配置阶段不决定构建类型,Debug/Release是在构建时选的;而 Ninja 这类单配置生成器相反,类型在配置阶段用CMAKE_BUILD_TYPE定死。混着用就会产生「我明明配了 Release,产物却是 Debug」的困惑——去看build/vs2022/Release/和build/vs2022/Debug/两个目录,大概率是构建时没带--config。

--parallel不带数字时用 CPU 核数,带数字则限流。老机器上盲目并行会把内存打满导致cl.exe被杀,报错是fatal error C1060: compiler is out of heap space,看到这个就把并行数降到 4 或 8。

安装这一步需要工程里先写规则,否则--install什么也不做:

# RUNTIME 对应 .exe 和 .dll,LIBRARY 对应 .lib/.so,ARCHIVE 对应静态库 install(TARGETS hello RUNTIME DESTINATION bin) install(TARGETS greeter ARCHIVE DESTINATION lib) install(DIRECTORY include/ DESTINATION include)

DESTINATION是相对--prefix的路径,写绝对路径会被拼到前缀后面,结果很怪。这三条规则写完,--install才会真的把 exe、lib、头文件按目录结构铺到目标位置。工程里如果还链接了动态库,别忘了把对应 DLL 也装进去,否则交付到干净机器上运行时报的不是编译错误,是「找不到 xxx.dll」,排查方向完全不同。

3.4 用预设文件把参数固化下来

命令行参数一多,靠记忆和复制粘贴必然出错。CMakePresets.json把生成器、架构、缓存变量、构建类型写成文件,进仓库,所有人共用一套。

{ "version": 6, "configurePresets": [ { "name": "msvc-release", "displayName": "MSVC x64 Release", "generator": "Visual Studio 17 2022", "architecture": "x64", "binaryDir": "${sourceDir}/build/msvc-release", "cacheVariables": { "CMAKE_CXX_STANDARD": "17" } } ], "buildPresets": [ { "name": "msvc-release", "configurePreset": "msvc-release", "configuration": "Release", "jobs": 8 } ] }

version: 6对应 3.25 这一代的 schema,往下兼容做得不错但不要跨大版本混用——把 3.25 生成的预设文件丢给 3.20 的 cmake 会直接拒绝解析,报 schema 版本不支持。binaryDir里的${sourceDir}是内置变量,展开成工程根目录;jobs控制并行度,团队里机器配置差异大时,把它写死比让每个人自己加--parallel更可控。

用起来就两条命令:

cmake --preset msvc-release cmake --build --preset msvc-release

预设的另一个好处是它自带版本校验。文件里可以声明cmakeMinimumRequired,版本不够时直接报错,而不是等到某个命令不认识才失败。这一点在多版本并存的机器上特别有用,等于把「必须用 3.25」这条约束写进了工程。

4. 第三方依赖怎么接:find_package、FetchContent 与 vcpkg 的分工

依赖管理是 CMake 工程最容易失控的部分。三种主流方式各有明确的适用面:系统里已经装好的库用find_package,需要固定版本的源码依赖用FetchContent,需要成套管理二进制依赖用包管理器加工具链文件。搞清楚边界,比背命令重要。

4.1 find_package 的两种模式与搜索路径

find_package有 Module 和 Config 两种模式,行为完全不同。Module 模式找的是share/cmake-3.25/Modules下的FindXXX.cmake,由 CMake 自己维护,覆盖一批常见库;Config 模式找的是库自己安装时带出来的XXXConfig.cmake,位置由库的安装前缀决定。

# CONFIG 强制走 Config 模式,避免找到同名的旧 Find 模块 find_package(SomeLib CONFIG REQUIRED) # 目标名一般是 包名::包名,用这个而不是老的 ${SomeLib_LIBRARIES} target_link_libraries(hello PRIVATE SomeLib::SomeLib)

显式写CONFIG是个好习惯。不写时 CMake 先试 Module 再试 Config,找到哪个用哪个;如果机器上恰好存在一个过时的FindSomeLib.cmake,它可能指向一个完全不同的版本,而报错信息不会告诉你是走错了模式。

找不到包时,先看报错里的搜索路径列表,那是它实际找过的目录。要额外加路径,用CMAKE_PREFIX_PATH:

cmake -S . -B build/vs2022 -G "Visual Studio 17 2022" -A x64 ` -DCMAKE_PREFIX_PATH="D:/libs/SomeLib;D:/libs/AnotherLib"

CMAKE_PREFIX_PATH是分号分隔的列表,不是逗号,也不是多次传参。每个条目会被展开成<prefix>/lib/cmake/<name>、<prefix>/share/<name>等若干候选位置。路径里有空格一定要加引号,PowerShell 里用反引号续行时更要注意,续行符后面不能有多余空格。

4.2 FetchContent:版本锁定与离线兜底

FetchContent在配置阶段把外部源码拉下来并add_subdirectory进去,适合还没进包管理、或者需要打补丁的依赖。

include(FetchContent) FetchContent_Declare( mylib GIT_REPOSITORY https://example.invalid/mylib.git GIT_TAG v1.2.0 # 用 tag 或 commit,别用分支名 GIT_SHALLOW TRUE # 只要最新一次提交,克隆更快 SYSTEM # 3.25 支持,把它的头文件当系统头,抑制警告 ) # 3.24 起支持:本机已装同版本时直接复用,不再克隆 FetchContent_Declare( mylib FIND_PACKAGE_ARGS 1.2 CONFIG ) FetchContent_MakeAvailable(mylib)

GIT_TAG写分支名是反复翻车的根源:同一份 CMake 配置,今天拉到的和下周拉到的可能不是同一份代码,构建结果不可复现,出问题也没法回滚到具体提交。用 tag 或 commit hash,写进仓库,这就是最便宜的后悔药。

FIND_PACKAGE_ARGS是 3.24 之后的能力,3.25 完整支持。它的逻辑是「先找本机已安装的,找不到再拉源码」,对 CI 和本地开发都友好——CI 上从缓存拿,本地首次构建才克隆。SYSTEM参数在 3.25 生效,把拉下来的目标标记为系统目标,编译器的警告不会再从第三方代码里冒出来淹没自己的告警。

离线环境要准备兜底:FETCHCONTENT_SOURCE_DIR_<NAME>可以指定一个已经存在的源码目录,跳过下载环节。

# 大写包名,横线换成下划线 cmake -S . -B build/vs2022 -G "Visual Studio 17 2022" -A x64 ` -DFETCHCONTENT_SOURCE_DIR_MYLIB="D:/vendor/mylib-src"

内网构建机没有外网时,把这个变量写进预设的cacheVariables,配合FETCHCONTENT_FULLY_DISCONNECTED=ON,配置阶段完全不尝试联网,失败信息也从「网络超时」变成明确的「源码目录不存在」,排查方向清晰得多。

4.3 vcpkg 工具链文件的挂载方式

需要管理的依赖一多,逐个find_package加路径就不现实了,这时候上包管理器。vcpkg 通过工具链文件接管编译器探测和依赖解析,挂载方式是配置时传CMAKE_TOOLCHAIN_FILE。

cmake -S . -B build/vcpkg -G "Visual Studio 17 2022" -A x64 ` -DCMAKE_TOOLCHAIN_FILE="D:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake" ` -DVCPKG_TARGET_TRIPLET=x64-windows

工具链文件必须在首次配置时传入。已经生成过CMakeCache.txt的构建目录再传这个参数会被忽略,表现是「明明传了却完全没生效」——这是最常见的一个坑,解决方法是删掉构建目录重新配置,或者用--fresh。

VCPKG_TARGET_TRIPLET决定链接静态库还是动态库:x64-windows是动态,x64-windows-static是静态。选错会得到链接期的一堆符号冲突,而且报错信息指向的是系统库,跟 vcpkg 毫无关系,非常难联想。工程里如果有交付到干净机器的需求,静态三元组能省掉打包 DLL 的麻烦,代价是体积变大。

较新的 vcpkg 工具链会读VCPKG_ROOT环境变量来自动定位自身,所以另一种更省事的写法是只设环境变量,不传参数:

$env:VCPKG_ROOT = "D:/tools/vcpkg" cmake -S . -B build/vcpkg -G "Visual Studio 17 2022" -A x64 ` -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake"

环境变量方式适合个人机器,团队协作还是写进预设更可靠。另外注意工具链文件和CMAKE_PREFIX_PATH可以共存,vcpkg 会把自己的安装目录追加进搜索路径,两者不冲突。

5. 踩坑实录:Windows 上这套包的五个高发故障

下面五条都是我在真实工程里反复遇到的,按「现象 → 原因 → 解决」记录。每条都附了判断依据,照着对号入座就行,不用从头试。

5.1 配置阶段的三个坑

坑一:cmake --version显示的不是 3.25。

现象是命令行里查到的版本对,但构建时报某个命令不存在,或者预设文件解析失败提示 schema 不支持。根因是进程继承的 PATH 与当前终端不一致,IDE、CI agent、计划任务各自有一份环境,安装版还会往系统目录写cmake.exe,优先级排在前面。

解决分三步:where.exe cmake列出全部候选;确认有几份、分别属于哪个目录;把要用的那份前置到$env:Path最前面,或者干脆用绝对路径调用。清完之后如果还不对,删掉构建目录重新配置——CMakeCache.txt里缓存了CMAKE_COMMAND的绝对路径,旧值会一直生效。

坑二:VS 生成器报找不到 Visual Studio 实例。

现象是-G "Visual Studio 17 2022"配置阶段直接失败,提示 could not find any instance。原因通常不是没装 VS,而是没勾「使用 C++ 的桌面开发」工作负载,只有 IDE 没有编译器;另一种是装了更新版本的 VS,生成器名字和实际版本对不上。

先在 VS Installer 里确认工作负载,再用vswhere查实际装了哪些实例和版本号,按查到的版本改生成器名。实在不想动 VS,就直接切 Ninja 加开发者命令行,绕开实例发现这一步。

坑三:Ninja 下找不到cl.exe。

现象是配置阶段报No CMAKE_CXX_COMPILER could be found。原因是 Ninja 不做编译器自动发现,它只认环境里已有的编译器,而普通 PowerShell 里没有 MSVC 的环境变量。

解决是用Launch-VsDevShell.ps1或VsDevCmd.bat先把环境拉起来,再执行配置。另一种写法是显式指定编译器全路径-DCMAKE_CXX_COMPILER=".../cl.exe",但不推荐——只指定了编译器,INCLUDE和LIB还是缺的,编译到一半才失败。

5.2 构建与产出阶段的两个坑

坑四:改了CMakeLists.txt,构建结果没变化。

现象是新增了源文件或者改了编译选项,重新构建却像什么都没发生。原因有两类:一是多配置生成器下构建时没带--config,实际构建的是另一个类型;二是缓存里存着旧的变量值,CMakeCache.txt的优先级高于你这次传的参数。

先看构建日志里实际用的配置和编译命令,确认类型对不对;再确认缓存变量,cmake -LA -N build/xxx能把缓存里所有变量列出来。3.25 上最省事的做法是cmake --preset xxx --fresh,--fresh会清掉缓存重建,但保留构建目录,比整个删掉再配置快。要提醒的是--fresh之后所有手动传过的缓存变量都没了,必须靠预设重新给全,这正是把参数写进预设的价值。

坑五:安装完程序起不来,报缺 DLL 或找不到头文件。

现象是构建全绿,--install也成功,到了目标机器上双击就报错。原因是多配置生成器下--install默认拿 Debug 产物,或者安装规则只写了 exe 没写依赖的 DLL,再或者头文件目录没写进install(DIRECTORY ...)。

先看--prefix目录里到底有什么,缺什么补什么规则;确认--install后面跟了--config Release;动态链接的第三方库要用install(IMPORTED_RUNTIME_ARTIFACTS ...)或者包管理器的拷贝机制一起带上。交付前我一般会在干净环境里跑一遍,宁可这里暴露也别让下游先发现。

6. 把构建变成可验证的流水线:workflow presets 与 CTest

单机跑通只是及格线,能一条命令从零到验证通过才算把工程交出去了。3.25 正好有 workflow presets,可以把配置、构建、测试、打包串成一次调用,配合 CTest 和 file API,脚本层面能拿到结构化的构建结果,不用再去 grep 日志。

6.1 workflow presets 串起四个阶段

在同一个CMakePresets.json里补上测试和 workflow 两个段落。workflowPresets的每一步引用的是对应类型的预设名,顺序执行,任一步失败就中断。

{ "version": 6, "testPresets": [ { "name": "msvc-release", "configurePreset": "msvc-release", "configuration": "Release", "output": { "outputOnFailure": true } } ], "workflowPresets": [ { "name": "ci", "steps": [ { "type": "configure", "name": "msvc-release" }, { "type": "build", "name": "msvc-release" }, { "type": "test", "name": "msvc-release" }, { "type": "package", "name": "msvc-release" } ] } ] }

调用只有一条:

cmake --workflow --preset ci

steps的type只能是 configure、build、test、package 四种,name必须指向对应类型下已定义的预设。outputOnFailure让测试失败时才打印输出,日志不会淹没在几十个用例的输出里。第 4 步的 package 需要工程里有 CPack 配置,没有的话删掉这一步,前三步足够覆盖日常验证。

这条命令的价值在于「零参数」。CI 脚本里不用维护一长串-D参数,本地复现 CI 失败也是同一条命令,环境差异被压到最小。我在几个工程里把它设成提交前必跑一次,省掉了大量「本地好的、CI 挂的」扯皮。

6.2 CTest 与 file API:让脚本问出结果

测试注册在CMakeLists.txt里:

# include(CTest) 会自动定义 BUILD_TESTING 选项,并调用 enable_testing() include(CTest) if(BUILD_TESTING) add_executable(t_greeter tests/t_greeter.cpp) target_link_libraries(t_greeter PRIVATE greeter) # 用例名带模块前缀,失败时一眼定位 add_test(NAME greeter.greet_returns_text COMMAND t_greeter) set_tests_properties(greeter.greet_returns_text PROPERTIES TIMEOUT 30) endif()

TIMEOUT值得加,Windows 上某些用例卡死时不会自己退出,CI 会一直挂着直到整体超时。查用例可以用ctest --test-dir build/msvc-release -C Release --output-on-failure -R greeter,-R按正则过滤,本地调试单个用例时很好用。

需要脚本层面消费构建结果时,用 file API。它的机制很朴素:在构建目录下放一个查询文件,重新配置一次,结果以 JSON 落在 reply 目录里。

# 查询文件名即查询类型,codemodel-v2 能拿到目标、源文件和产物路径 New-Item -ItemType Directory -Force .\build\msvc-release\.cmake\api\v1\query | Out-Null Set-Content .\build\msvc-release\.cmake\api\v1\query\codemodel-v2 "" cmake --preset msvc-release # index 文件里有指向具体回复文件的引用 Get-ChildItem .\build\msvc-release\.cmake\api\v1\reply | Select-Object -First 5 Name

查询文件必须是空文件,文件名就是查询类型;除了codemodel-v2,常用的还有cmakeFiles-v1(构建系统文件清单)和toolchains-v1(编译器与工具链信息)。reply 目录里的index-*.json是入口,顺着里面的jsonFile字段找具体内容,别直接去猜文件名。这套机制的好处是不依赖日志格式,日志改了脚本不会崩,适合把产物路径喂给后续的签名、打包、部署环节。

一个实际习惯:新工程接入时,我会先跑一遍cmake --workflow --preset ci,再用 file API 把 codemodel 拉出来核对目标名和产物路径,确认脚本里写死的路径和实际产出对得上。有一次产物目录多了个配置子目录,部署脚本一直拷的是空目录,查了半天才发现是脚本里的路径写死了。从那以后我每接一个新工程,配置、构建、测试、路径核对这四件事都强制走一遍,再动手写业务代码。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进