CMake 3.24.0 源码编译安装与 CMakePresets 流水线实践 简介CMake 3.24.0 源码归档包面向 C 开发者、跨平台项目维护者与需要统一构建流程的团队提供从源码编译安装 CMake 的完整材料可用于研究构建系统实现、定制编译选项或在多编译器环境中搭建一致工程基础设施缓解多平台构建脚本重复维护的问题。压缩包约 9.91MB共 2000 个文件以 txt 说明与测试数据、cmake 模块脚本、rst 文档、c/h/cxx/cpp 源码及 json、py、sh 配置辅助脚本为主另含测试样例和多平台工程文件覆盖核心源码、模块、文档与测试目录。已有 427 人学习适合深入理解 CMake 内部机制、查阅模块写法或完成本地构建的读者也适合需要提升跨平台工程组织能力的中高级 C 开发者较完整的目录结构与示例便于按模块检索源码、对照文档研究变量和命令实现为自定义模块、依赖查找与跨平台构建实践提供参考。1. 为什么要自己编一个 CMake 3.24.0从一台老构建机说起一台跑了多年的构建机系统自带的 cmake 还停在 3.10仓库里的 CMakePresets.json 一提交上去就报 preset 版本不支持FetchContent 的 FIND_PACKAGE_ARGS 被当成未知参数默默忽略。想升级包管理器一动就是整套工具链跟着变谁也不敢在这台机器上先按回车。cmake-3.24.0.tar.gz 这类源码包就是给这种局面准备的不碰系统包编一份装到 /opt 下PATH 一切换一台机器上两个 cmake 各干各的活。它适合要在一台机器上锁死特定 CMake 版本的构建维护者也适合需要在无外网环境里从零复现构建链路的团队。后面按解包、bootstrap、装前缀、排错、用新特性、写进流水线的顺序走一遍。2. 解包与 bootstrap从 tar.gz 到可执行 cmake 的三段式2.1 包里有什么编译前要备什么先把包拆开看结构别急着./bootstrap。源码包的顶层目录名带版本号进错目录是新手第一个跟头。# 解包并进入源码根目录 tar -xzf cmake-3.24.0.tar.gz cd cmake-3.24.0 # 看一眼顶层结构确认关键目录都在 ls -1正常输出里应该能看到Bootstrap.cmake、CMakeLists.txt、Help、Modules、Source、Templates、Tests、Utilities这几项。Modules是整份资源里最值钱的部分——find_package的几百个查找脚本全在这安装后落到share/cmake-3.24/ModulesSource是 cmake 自身的 C 实现Utilities下挂着内置的第三方库常见的有cmcurl、cmlibarchive、cmjsoncpp、cmzlib、cmbzip2、cmexpat这些决定了后面第 3 章的开关怎么选。编译前需要备齐的东西不多但少一样就卡住# 编译器需要支持 C11GCC 4.8 及以上、Clang 3.7 及以上都可以 gcc --version | head -1 # 构建工具 make --version | head -1 # 可选OpenSSL 开发头文件默认构建路径会用到 ls /usr/include/openssl/ssl.h 2/dev/null echo openssl headers ok头三条里最容易缺的是最后一条。很多构建镜像只装了 OpenSSL 的运行时库没装开发头文件bootstrap 阶段发现不了等到 make 编内置 curl 时才炸具体现象放在第 4 章讲。提示源码包本身不依赖机器上已装的 cmake这就是 bootstrap 存在的意义。所以哪怕系统 cmake 是 2.x也能编出 3.24。2.2 bootstrap 到底做了什么参数怎么给一条命令看起来简单但它内部其实跑了三趟先用系统编译器把一小撮核心源文件编成一个「最小 cmake」再用这个最小 cmake 去 configure 整个工程最后 make 出完整版本。所以总耗时大概是同等规模 C 工程的两倍第一次编的人常常以为卡死了。# 三段式的第一段引导 生成 Makefile ./bootstrap --prefix/opt/cmake-3.24.0 \ --parallel$(nproc) \ --enable-ccache \ -- -DCMAKE_BUILD_TYPERelease \ -DCMAKE_USE_OPENSSLON \ -DBUILD_TESTINGOFF参数分两拨--之前是 bootstrap 自己的选项之后是透传给 CMake 的缓存变量两者不能混写。--prefix决定安装位置也是多版本共存的唯一依据--parallel只影响 bootstrap 生成的那份 Makefile 的默认并行度后面make -j还会覆盖它--enable-ccache要求 PATH 里真的有 ccache没有就当场报错退出-DBUILD_TESTINGOFF省掉一大堆测试目标的配置时间自己编工具链没必要全开。参数作用建议值--prefix安装前缀决定最终目录布局/opt/cmake-3.24.0--parallel生成的 Makefile 默认并行度nproc或内存允许的值--enable-ccache用 ccache 包装编译器重复编译同一版本时开--bootstrap-verbose打印每一步实际执行的编译命令排错时临时加--system-curl/--no-system-curl用系统 libcurl 还是内置见第 3 章--之后的-D...透传给 CMake 的缓存变量构建类型、OpenSSL 开关然后是后两段。make install需要写/opt的权限一般得 sudo安装完构建目录里会留一份install_manifest.txt记录所有被写出去的文件绝对路径这是以后卸载的唯一凭据删了就只能靠猜。# 第二段编出完整 cmake注意 -j 要按内存给 make -j$(nproc) # 第三段安装到前缀目录 sudo make install2.3 安装后接管 PATH别让 CMAKE_ROOT 迷路装完先直接拿绝对路径验一次绕开 PATH 干扰# 绝对路径调用排除 PATH 影响 /opt/cmake-3.24.0/bin/cmake --version预期输出第一行是cmake version 3.24.0。确认无误再改环境# 前置到 PATH并清掉 shell 的命令哈希缓存 export PATH/opt/cmake-3.24.0/bin:$PATH hash -r type -a cmaketype -a会把 PATH 里所有叫 cmake 的都列出来第一个才是真正生效的。如果第一个还是/usr/bin/cmake说明前面某处还有更靠前的路径在捣乱去查/etc/profile.d和 CI 的 env 配置。还有一个隐蔽的坑cmake 二进制启动时会根据自身位置推导CMAKE_ROOT去找../share/cmake-3.24。所以只把bin/cmake拷到别的目录是用不了的会直接报Could not find CMAKE_ROOT。要拷就拷整个前缀打包镜像时尤其注意。# 确认 CMAKE_ROOT 指向新装的前缀 cmake -E capabilities | head -c 200输出是一段 JSON里面的version字段能同时验证版本号generators字段能看出这个构建支持哪些生成器。这一步比--version更有信息量因为--version只证明二进制能跑capabilities证明它加载到了正确的 Modules 目录。3. 编译期开关OpenSSL、zlib、libarchive 怎么取舍3.1--system-*不是二选一而是「优先不优先」很多人以为--system-curl和--no-system-curl是硬开关实际语义是「系统上有就用没有就用内置」。也就是说默认构建出来的 cmake 到底链接了谁取决于编译那台机器上装了什么这是一份典型的黑匣子。编完用 ldd 查一次比看文档猜靠谱。# 看最终二进制到底动态链接了哪些第三方库 ldd /opt/cmake-3.24.0/bin/cmake | grep -E curl|archive|libz|ssl|crypto如果输出里出现libcurl.so.4、libarchive.so.13说明用的是系统库这份 cmake 搬到另一台机器上就可能起不来。想让产物「拷走就能跑」编译时必须显式关掉所有系统库选项。开关影响的功能什么时候关--system-zlibcmake -E tar的 gzip 支持产物要拷到别的机器--system-bzip2cmake -E tar的 bzip2 支持同上--system-expat解析 XML 类描述文件同上--system-libarchive归档文件读写cmake -E tar主力同上--system-curlfile(DOWNLOAD)、FetchContent 的 https 下载产物要自包含--system-jsoncppcmake -E capabilities等 JSON 输出一般无所谓另一个维度是 OpenSSL。-DCMAKE_USE_OPENSSLON只影响内置 curl 的 TLS 后端跟 cmake 本体无关。开了它编出来的 cmake 才能通过 https 拉依赖代价是编译机必须有 OpenSSL 开发头文件且产物动态链接 libssl/libcrypto。3.2 三套典型组合直接对应三种场景第一套给内网离线机全部用内置库产物自包含拷到任何同架构机器上都能跑# 全内置产物不依赖目标机的第三方库 ./bootstrap --prefix/opt/cmake-3.24.0 --parallel$(nproc) \ --no-system-curl --no-system-libarchive --no-system-zlib \ --no-system-bzip2 --no-system-expat --no-system-jsoncpp \ -- -DCMAKE_BUILD_TYPERelease \ -DCMAKE_USE_OPENSSLON \ -DBUILD_TESTINGOFF第二套给 CI 镜像瘦身系统库都用上编译快、体积小但镜像里必须带齐对应的运行时包# 尽量复用系统库编译产物更小更依赖环境 ./bootstrap --prefix/usr/local --parallel$(nproc) \ --system-curl --system-libarchive --system-zlib \ -- -DCMAKE_BUILD_TYPERelease \ -DBUILD_TESTINGOFF第三套最省事只做版本升级其它保持和系统一致适合「系统里有 cmake但版本不够」的机器# 只换版本依赖关系跟系统包保持一致 ./bootstrap --prefix/opt/cmake-3.24.0 --parallel$(nproc) \ -- -DCMAKE_BUILD_TYPERelease \ -DCMAKE_USE_OPENSSLOFF \ -DBUILD_TESTINGOFFCMAKE_USE_OPENSSLOFF那条要留意内置 curl 会退化成不支持 https。如果项目里 FetchContent 全靠file://本地镜像或 git 协议这没问题一旦要拉 https 资源会在下载阶段失败并给出一句很含糊的报错属于典型的踩坑点。3.3 ccache 与并行把两遍编译的时间砍下来bootstrap 的两遍编译特性决定了它特别吃缓存。同一台机器上反复换参数重编时--enable-ccache能省掉接近一半的时间# 确认 ccache 在 PATH 里否则 bootstrap 会直接失败 which ccache || echo 先装 ccache 再开 --enable-ccache # 指定编译器并开启缓存 export CCgcc CXXg ./bootstrap --prefix/opt/cmake-3.24.0 \ --parallel$(nproc) \ --enable-ccache \ -- -DCMAKE_BUILD_TYPERelease \ -DBUILD_TESTINGOFFCC/CXX环境变量在 bootstrap 阶段就会被读取比后面传-DCMAKE_CXX_COMPILER更早生效是切换编译器的正确姿势。并行度有个反直觉的点--parallel$(nproc)在 64 核机器上会直接把内存打爆因为 cmake 源码里有几个翻译单元单个进程就能吃掉 1G 以上具体阈值和现象第 4 章展开。编完顺手减个肥安装目录能从几百兆降到几十兆# 去掉调试符号注意只对 bin 下的可执行文件做 sudo strip /opt/cmake-3.24.0/bin/cmake du -sh /opt/cmake-3.24.04. 排查bootstrap 与安装后的五种翻车现场4.1 编译阶段挂掉头文件、内存与编译器版本第一种OpenSSL 头文件缺失。现象是make跑到Utilities/cmcurl相关目标时停住报fatal error: openssl/ssl.h: No such file or directory。原因是 bootstrap 默认让内置 curl 走 OpenSSL 后端而机器上只装了运行时库。解决办法两条装开发包或者干脆-DCMAKE_USE_OPENSSLOFF放弃 https 下载能力。我一般选前者因为网络能力关掉以后排错更费劲。第二种编译到一半进程被 Killed。现象是make输出里突然出现internal compiler error或者干脆一行Killed然后构建中断。原因是并行度超过了内存上限这种情况没有任何提示指向内存最容易被误判成编译器 bug。查证方式是看内核日志有没有 OOM 记录解决方式是把-j降到 4 甚至 1 重跑——make会从断点继续不用重新 bootstrap。# 从中断处继续降低并行度避免再次 OOM make -j4 # 事后确认是不是内存杀的 dmesg | tail -20 | grep -i -E oom|killed第三种编译器太老。现象是 bootstrap 一开始就报CMAKE_CXX_COMPILER不支持 C11 之类的信息。原因是默认g版本低于 GCC 4.8。解决方式是CC/CXX指向机器上更高版本的编译器或者先升级工具链别指望改-DCMAKE_CXX_FLAGS能绕过编译期检查在 bootstrap 里就做了。# 用明确版本号的编译器别依赖默认值 export CC/usr/bin/gcc-9 CXX/usr/bin/g-9 ./bootstrap --prefix/opt/cmake-3.24.0 -- -DCMAKE_BUILD_TYPERelease4.2 运行阶段挂掉PATH 缓存与动态库第四种装完还是旧版本。现象是cmake --version输出 3.10但/opt/cmake-3.24.0/bin/cmake --version明明是 3.24.0。原因通常有两个叠加PATH 顺序不对以及 shell 缓存了旧路径。hash -r之后再用type -a cmake确认第一个是新的那个。CI 里还有第三种可能——某个脚本里写死了/usr/bin/cmake这种只能全局搜一遍改掉。这一点在 presets 场景下会表现为更迷惑的报错比如提示 preset 版本不受支持看着像文件写错了其实是解释器用错了。第五种libcurl.so.4找不到。现象是执行/opt/cmake-3.24.0/bin/cmake直接报error while loading shared libraries二进制根本起不来。原因是编译时用了--system-curl动态链接了系统 libcurl而产物被拷到了另一台机器或精简镜像里。解决办法是回到编译期重新用--no-system-curl编一份自包含的临时用LD_LIBRARY_PATH顶一下可以但别写进长期脚本下一个人接手时会疯。# 重新编一份完全自包含的产物 ./bootstrap --prefix/opt/cmake-3.24.0 --parallel8 \ --no-system-curl --no-system-libarchive --no-system-zlib \ -- -DCMAKE_BUILD_TYPERelease -DBUILD_TESTINGOFF # 复核这一条应该没有输出 ldd /opt/cmake-3.24.0/bin/cmake | grep -E libcurl|libarchive5. 3.24 的新东西--fresh、依赖提供者与 FIND_PACKAGE_ARGS5.1cmake --fresh终于不用手删 CMakeCache.txt老版本改配置时很多人习惯rm -rf build一把梭代价是编译缓存全丢。3.24 起多了个--fresh它只删掉缓存和中间目录重新走一遍 configure其它产物保留。# 先按 Release 配一版 cmake -S . -B build -DCMAKE_BUILD_TYPERelease # 换成 Debug 重配不丢编译缓存 cmake --fresh -S . -B build -DCMAKE_BUILD_TYPEDebug这个选项的边界很清楚它清理的是build目录下的 CMakeCache.txt 和 CMakeFiles 目录不动你生成的文件。写进脚本后就没法在 3.23 及更低的机器上跑了所以要么在脚本开头做版本判断要么在文档里写清楚依赖。常见做法是配一个 wrapper 脚本老 cmake 走rm -rf新 cmake 走--fresh。5.2 依赖提供者把 find_package 统一接管CMAKE_PROJECT_TOP_LEVEL_INCLUDES加上cmake_language(SET_DEPENDENCY_PROVIDER ...)是 3.24 里最被低估的组合。它允许你在顶层注入一个脚本拦截所有find_package调用把「找不到就报错」变成「找不到就从内部镜像拉源码」。# deps_provider.cmake # 拦截 find_package命中白名单的包改走 FetchContent cmake_language(SET_DEPENDENCY_PROVIDER dep_provider SUPPORTED_METHODS FIND_PACKAGE) function(dep_provider method package_name) if(${package_name} STREQUAL fmt) include(FetchContent) FetchContent_Declare(fmt SOURCE_DIR /opt/mirror/fmt # 内网镜像目录不联网 ) FetchContent_MakeAvailable(fmt) else() # 不在白名单里的交回原生逻辑 find_package(${package_name} ${ARGN} REQUIRED) endif() endfunction()启用方式是把它挂到顶层变量上# 注意路径必须是绝对路径且只在顶层 configure 时生效 cmake -S . -B build \ -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES$PWD/deps_provider.cmake这里有两点必须记住其一依赖提供者只有在顶层cmake_minimum_required()声明了不低于 3.24 的版本时才生效光装新 cmake 不够其二白名单之外的包一定会回落到原生find_package所以函数末尾那句不能省否则整套依赖查找都会静默失效。这个特性最适合的场景是几十个内部子工程共享同一份依赖来源改一处全生效。5.3FIND_PACKAGE_ARGS先找系统包找不到再拉源码3.24 给FetchContent_Declare加了FIND_PACKAGE_ARGS语义是「先试着find_package成功了就用系统装好的失败了再按声明的方式拉源码」。include(FetchContent) # 优先用系统已装的 GTest 1.11找不到才从本地镜像拉 FetchContent_Declare( googletest SOURCE_DIR /opt/mirror/googletest FIND_PACKAGE_ARGS 1.11 NAMES GTest ) # 这一步会先走 find_package(GTest 1.11 NAMES GTest) FetchContent_MakeAvailable(googletest)FIND_PACKAGE_ARGS后面的内容原样传给find_package所以版本约束、NAMES、COMPONENTS都能写。这项改动在 CI 上收益最明显镜像里预装了系统包时省掉一次源码编译没装时行为跟以前一致不会突然失败。要留意的边界是它只对FetchContent_MakeAvailable生效还在用FetchContent_Populate的老脚本不会走这条路径得先改成新写法。6. 锁住行为用 CMakePresets v5 把 3.24 写进流水线6.1 一份最小可用的 presets v5装好 3.24 之后把版本要求写进仓库比在文档里写一句话管用得多。v5 是 3.24 才能读的版本号一旦写了 53.23 及以下会直接拒绝解析这正好是一道硬性拦截。{ version: 5, cmakeMinimumRequired: { major: 3, minor: 24, patch: 0 }, configurePresets: [ { name: base, hidden: true, generator: Ninja, binaryDir: ${sourceDir}/build/${presetName}, cacheVariables: { CMAKE_BUILD_TYPE: Release, CMAKE_EXPORT_COMPILE_COMMANDS: ON } }, { name: linux-release, inherits: base, toolchainFile: ${sourceDir}/cmake/toolchain-linux.cmake } ], buildPresets: [ { name: linux-release, configurePreset: linux-release, jobs: 8 } ] }cmakeMinimumRequired和version要一起写前者决定了报错信息长什么样后者决定了老版本能不能读进来。只写version: 5不写cmakeMinimumRequired遇到旧 cmake 时报错会非常含糊接手的人得翻半天才知道该装哪个版本。# 配置与构建都走 preset命令行参数尽量不手写 cmake --preset linux-release cmake --build --preset linux-release6.2 一份两分钟的验证清单编完装完别急着宣布可用把下面四项挨个过一遍。整套动作熟练之后两分钟以内能挡掉后面几周的大部分电话。检查项命令期望结果版本与路径type -a cmake第一项指向/opt/cmake-3.24.0/bin/cmake能力加载cmake -E capabilities | head -c 200JSON 中版本为 3.24依赖归属ldd .../bin/cmake | grep -E curl|archive自包含构建时无输出安装清单ls build/install_manifest.txt文件存在卸载全靠它需要回滚到系统 cmake 时直接用安装清单反删比手动找文件干净# 在构建目录下执行清单里记录的是绝对路径 sudo xargs rm -f install_manifest.txt # 清掉 PATH 里的前缀恢复系统版本 export PATH$(echo $PATH | tr : \n | grep -v cmake-3.24 | paste -sd:) hash -r拿到这份 cmake-3.24.0.tar.gz 之后我建议先核对一下包的哈希值再按第 2 章的顺序走一遍全内置构建把第 4 章的五个检查点跑通最后才把 presets 提交进仓库。那台老构建机出过一次事故容器镜像里只拷了bin/下的二进制构建跑到一半报Could not find CMAKE_ROOT排查了整晚才发现是打包脚本的问题。从那以后我每次编完 cmake 都强制走一遍ldd加type -a的检查两分钟换来的是一整条流水线不用人盯着。希望帮到你。本文还有配套的精品资源点击获取