尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CMake从入门到实践:安装、语法、GUI与避坑全攻略

CMake从入门到实践:安装、语法、GUI与避坑全攻略 写 C 的人应该都经历过那种痛苦时刻项目写大了编译命令从一条变成几十条换个平台又得重来第三方库还在那头等着被链接。我第一次认真学 CMake是从一个开源项目开始的——当时根本不知道它是啥只知道照着 README 敲几个命令就能把项目跑起来。后来才慢慢想明白CMake 这类工具就是 C 项目的装修图纸它不负责怎么砌墙、贴砖真正的编译由底层编译器完成它负责把我要一个什么样的房子转成具体的施工方案也就是 Makefile、Ninja 工程、VS 工程这些内容。今天这篇笔记我就把 CMake 从安装到语法、从 GUI 到命令行、从常见报错到避坑心得一次性做一次系统性盘点。1. CMake 到底是什么为什么值得专门写一篇笔记1.1 从一个让人头大的场景说起假设你写了一个要同时跑在 Windows、Linux 和 macOS 上的 C 程序依赖了几个第三方库还想用上 C17。如果没有构建系统你需要手写三套编译脚本每个平台一套每换一个库还得去翻它的头文件和链接库路径。这还不算完等哪天换 IDE比如从 Visual Studio 换到 CLion整个配置几乎要重来。这种换个环境就得重新折腾一遍的体验我相信每个 C 开发者都印象深刻。CMake 解决的正是这个反复折腾的问题。你在 CMakeLists.txt 里只描述一次项目结构、依赖关系和编译选项CMake 就会基于当前机器的工具链生成对应的构建文件。同一个 CMakeLists.txt在 Windows 上可以生成 VS 工程在 Linux 和 macOS 上可以生成 Makefile 或 Ninja 工程。你不需要修改项目源文件只需要重新执行一次配置和构建流程。1.2 CMake 到底做了什么它在三层结构里的位置这里说清楚一个特别容易被初学者绕晕的问题CMake 不是编译器也不是一门通用编程语言而是构建系统的生成器。它的语法上更像一种有函数调用和变量的壳语言里面常见的 add_executable、target_link_libraries 这些命令本质上是在告诉 CMake构建规则长什么样而不是直接去编译或者链接代码。理解这个层级关系后面遇到配置报错就不慌源文件交给编译器处理CMakeLists.txt 交给 CMake 处理CMake 生成 Makefile 后再由 make 或者 ninja 调用编译器完成实际构建。很多时候你在终端看到 Make Error 或者一堆 g 报错那其实是 make 阶段出现的编译问题跟 CMake 没关系。排查问题的第一步就是确认到底卡在哪一层。1.3 为什么项目规模一大CMake 的收益就越明显小项目里你确实可以靠一行编译命令坚持很久。一个 main.cpp 而已g main.cpp -o app 就完事。但一旦项目出现 include 目录、静态库、动态库、外部依赖这条命令会变得无比脆弱——路径多一个空格、库顺序不对、宏定义少了都会让你排查掉大量时间。而这些恰好是 CMake 最擅长的领域它把编译细节写成了结构化声明谁接手理解起来都不难。再往后项目如果用 CI/CD 自动构建或者多个模块共用一套构建规则CMake 的分层配置能力就体现出来了顶层 CMakeLists.txt 定义整个工程子目录各管各的 add_subdirectory公共选项通过 option 一次性暴露出来。这种可维护性是手写编译脚本完全不具备的。所以现在来找 CMake 资料的人基本是这几个方向安装、基础语法、GUI 操作、IDE 集成、第三方库。下面我按这个顺序把这几个方向完整整理一遍都是自己实际用下来觉得值得记录的。2. 环境准备安装、版本、工具链一个都不能少2.1 各平台安装 CMake 的正确姿势先说明一个观点CMake 的安装本身不难难的是装了对的版本。CMake 的版本直接影响可用语法很多老旧教程用的 3.10 之前的老写法在 3.20 以上的新版会看到警告甚至直接报错。Windows 平台官网下载 msi 是一个选择但我更推荐用 winget。在 Win10、Win11 的终端里输入 winget install Kitware.CMake装完会自动加入 PATH。直接从官网下载 msi 安装的话有个坑如果安装时忘了勾选 Add CMake to system PATH后面命令行会完全找不到 cmake 命令还得手动去改系统环境变量。我用 winget 实测下来它会默认处理好这些对新手更友好。Ubuntu 下最快的当然是 apt install cmake但 Ubuntu 仓库里的版本往往偏老。比如 Ubuntu 22.04 默认的 CMake 还是 3.22指向 3.28 的新项目可能就会在 policy 设置或者某些新命令上卡住。要装新版本可以到官网下载源码自己编译也可以用 pip 的方式安装。pip install cmake这里装的其实不是 Python 库而是 CMake 官方发布的二进制包装完直接在命令行就有 cmake 用这个方式比较隐蔽但很好用。如果是完全离线的环境我的建议是先下载官方发布的 tgz 源码包解压后按 ./configure make make install 编译安装。编译安装时加上 --prefix/usr/local/cmake-版本号避免去覆盖系统自带的 /usr/bin/cmake以后想切换版本也方便。macOS 下则最简单brew install cmake 就完事了。2.2 工具链匹配MinGW、Visual Studio、Clang 怎么选CMake 本身不编译代码它生成的构建文件要交给具体的编译器去执行。所以你在 Windows 上用 VS 就得选 VS 对应的编译器如果你用 MinGW则需要手动指定生成器。这里很容易踩坑的是架构不匹配如果你的 MinGW 是 32 位的CMake 是 64 位的后面编译器检测会直接失败。我在 VSCode 里反复切换编译器的阶段被这个问题折腾过后来统一成同一个架构的版本一切才顺起来。常见的组合参考Windows Visual StudioCMake 默认就能检测到 VS 版本并生成 VS 工程这种组合最省心基本不需要配置额外参数。Windows MinGW构建时必须指定 -G MinGW Makefiles而且要先保证 mingw32-make 在 PATH 里否则配置能通过构建阶段会直接报找不到 make。Linux gcc/g默认生成 Unix Makefiles什么都不用改。macOS Clang同样默认生成 Unix Makefiles开箱即用。2.3 工具链检测失败时的排查思路见到最多的报错大概是这个长相CMAKE_CXX_COMPILER is not set或者 could not find any compiler。我的排查顺序基本固定先确认编译器是否在 PATH在终端里分别输入 gcc --version、cl --version。再确认编译器和 CMake 的架构一致这件事可以通过在终端里输入 gcc --version 和 cmake --version 对比信息来确认。如果前两步没问题直接给 CMake 指定编译器cmake -DCMAKE_C_COMPILERgcc -DCMAKE_CXX_COMPILERg。最后删掉 build 目录重新配置。旧的 CMakeCache.txt 会残留上一次的编译器路径这个问题特别隐蔽也最常遇到。这四条看着简单实际能解决九成以上的工具链问题。尤其最后一条很多人改了编译器之后发现死活不起作用其实就是缓存里的旧路径一直在帮忙。3. 核心语法速览几行就够入门3.1 最小 CMakeLists.txt 骨架所有 C 项目的 CMakeLists.txt开头几行通常都是固定的cmake_minimum_required(VERSION 3.16) project(MyApp VERSION 1.0.0 LANGUAGES CXX) add_executable(myapp main.cpp)说明一下为什么版本不是越高越好cmake_minimum_required 声明的版本决定了很多语法策略的默认行为。版本太低一些新语法会被当成扩展而不够严谨版本太高又可能直接拒绝在较老的 CMake 环境里运行。我自己的习惯是 3.16 作为一个稳妥的基线C20 的项目才会往 3.20 以上走。project 这行里的 VERSION 不是必须的但写上之后工程版本号会在很多地方自动生效比如生成版本头文件、打包信息提前写上是好事。3.2 add_executable 和 add_library把源文件组织起来add_executable 负责生成可执行文件add_library 负责生成库。两者语法几乎一样add_library(mylib STATIC src/utility.cpp) add_executable(myapp src/main.cpp) target_link_libraries(myapp PRIVATE mylib)这里的 PRIVATE 是 CMake 3.0 之后才有的可见性机制。如果你写的 target_link_libraries 不带可见性CMake 会给出警告兼容性也有隐患。正确用法就是 PRIVATE / PUBLIC / INTERFACE 三选一具体区别是PRIVATE链接关系仅在当前 target 内部生效不会被依赖它的 target 继承。PUBLIC当前 target 用到的库也会传递给依赖它的 target。INTERFACE当前 target 本身不直接用到这个库但要求所有依赖它的 target 必须引入。初学者先记住 PRIVATE 是大多数场景的合理选择后面按需求调整。3.3 变量与路径写复杂项目前的必修课set 和 ${} 这两个语法大概 80% 的 CMakeLists 都用得到set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)这里的作用是强制编译器按 C17 标准来编译。CMAKE_CXX_STANDARD_REQUIRED 设为 ON 时如果编译器不支持该标准CMake 会直接报错而不是静默降级。这能避免为什么我用了 C17 语法还能过但到别人电脑上就报错这种诡异情况。路径方面CMAKE_SOURCE_DIR 是 CMake 内置变量指向工程根目录CMAKE_BINARY_DIR 指向构建目录。还有一个 CMAKE_CURRENT_SOURCE_DIR在多级子目录里尤其重要因为它指向当前 cmake 文件所在目录而不是整个工程的根目录。如果你写子模块的 CMakeLists.txt 时误用了 CMAKE_SOURCE_DIR大概率会引用到错误路径。这里我要特别强调一点尽量用 target_include_directories 而不是 include_directories。前者是作用到某个模块后者是全工程生效。滥用全局 include 会造成依赖不清晰、编译选项互相干扰现代 CMake 官方文档也明确推荐 target_ 前缀的版本。3.4 find_package让第三方库找得又快又稳找第三方库find_package 是标准动作也是 CMake 生态里的一大门类。比如引入 Eigen3 这个头文件库的典型写法是find_package(Eigen3 3.3 REQUIRED NO_MODULE) target_link_libraries(myapp PRIVATE Eigen3::Eigen)find_package 执行时会在预置的模块目录里搜索 FindEigen3.cmake 脚本或者系统安装路径下的 Eigen3Config.cmake 配置文件。找到之后会把头文件路径和链接库信息定义成一系列变量供后面的 target_link_libraries 使用。这里让初学者最困惑的是 Config 模式和 Module 模式的差别简单说Module 模式靠 FindXxx.cmake 文件Config 模式靠库自带的 XxxConfig.cmake。现在主流官方的库基本走 Config 模式所以很多跨平台库你只需要指定 REQUIREDCMake 就会自动优先找 Config。如果 find_package 找不到库大概率不是库没装而是库的 CMake 配置文件不在默认搜索路径里。排查方式有两个方向一是确认库是否真的装了二是检查库的 cmake 配置文件所在目录是否已经加进 CMAKE_PREFIX_PATH。很多初学者以为装了库就一定找得到其实很多包管理器把配置文件装在非标准路径下差一步就不认识。3.5 条件判断与 option控制结构的正确用法CMake 不是完整编程语言但常用的流程控制它都有。最实用的是 optionoption(USE_OPENSSL Use OpenSSL OFF) if(USE_OPENSSL) target_compile_definitions(myapp PUBLIC USE_OPENSSL1) endif()这样的好处是把所有可选能力集中在文件顶部构建者通过命令行加 -DUSE_OPENSSLON 就能开功能完全不需要改源文件。很多大型库的顶层 CMakeLists 上百行大部分逻辑其实都是这种 if/option 的组合。等你看到那些巨型项目的构建配置就不会觉得那是天书了它底层结构都差不多。4. 图形化操作CMake GUI 与 VSCode 插件的正确打开方式4.1 CMake GUI 的工作流程与适用场景搜 CMake 的人经常会带一个cmake gui的关键词。CMake GUI 本质上是 CMake 配置过程的图形化界面帮你填源码目录和构建目录剩下的还是命令行那一套。流程固定填入源码目录也就是 Where is the source code。填入构建目录也就是 Where to build the binaries。点 Configure选择生成器比如 Visual Studio 或 MinGW Makefiles。等配置通过后点 Generate 生成构建文件。最后回到命令行执行 make 或者 ninja或者直接打开生成的 VS 工程。整个 GUI 最有用的地方其实是 Configure 之后的那个变量列表。有些库的编译选项只存在于 CMake 内部比如 BUILD_SHARED_LIBS、USE_XX、OPENSSL_ROOT_DIR你光凭命令行去猜变量名很难GUI 里直接搜索勾选一目了然。比如引入 raylib 这类跨平台多媒体库第一次配置时打开 GUI 看一下选项比自己翻文档找变量名高效太多。需要注意一个细节GUI 生成的缓存文件是落在构建目录里的如果你在 GUI 里改过输出目录后来又跑到命令行去构建一定记得删掉构建目录里的 CMakeCache.txt否则它会继续用旧缓存你改了等于白改。4.2 VSCode CMake Tools 插件Configure 按钮到底在哪vscode安装cmake tools 底部状态栏应该有configure按钮吗这个问题我看着就觉得亲切因为我刚开始用 VSCode 写 C 时也困惑过。结论是CMake Tools 插件装好后底部状态栏确实会显示当前使用的编译器、构建目标、构建按钮但这些按钮都建立在 CMake 已经成功配置的基础上。如果你还没配置过底部可能只会显示未选择编译器或者根本没有 Configure。标准流程是安装 C/C 扩展和 CMake Tools 扩展。打开包含 CMakeLists.txt 的源代码目录。按 CtrlShiftP输入 CMake: Select a Kit选择一个编译器。再执行 CMake: Configure底部状态栏有时候会出现对应按钮没有也没关系命令面板执行时效果一样。配置完成后底部状态栏会正常出现 Build、Launch 等按钮此刻 Configure 按钮就不再需要频繁点了。这里有一个很多人踩过的细节CMake Tools 插件依赖 CMake 在 PATH 里但如果你用的是特殊安装方式还得在扩展设置里指定 cmake.executable 路径。我在 Windows 上遇到 VSCode 状态栏一直不显示 Configure 按钮后来排查出来就是安装 CMake 时没把路径加进 PATH 引起的。4.3 GUI 与命令行怎么配合效率最高我自己实际使用的频率大概是90% 命令行10% GUI。命令行适合反复测试、快速修改GUI 适合查看变量、调试 CMake 内部的配置值。还有一种场景是跨平台库的首次配置比如前面说的 raylib用 GUI 打开它的 CMake 选项列表看一眼比自己猜变量名高效得多。等你习惯了命令行再需要看配置项时就用 cmake -LA 命令把缓存里的变量列出来也基本可以达到 GUI 的效果。5. 实操全流程从零搭一个带库的 C 项目5.1 项目结构设计实操比理论重要我用一个最典型的库 可执行程序结构带大家走一遍demo/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ └── utility.cpp └── include/ └── utility.h这个结构代表大多数中小项目的形态公共头文件放 include实现放 src顶层只有一个 CMakeLists.txt。如果以后要拆多个模块再加子目录并配合 add_subdirectory 就行。初期不建议搞那种特别复杂的多层目录结构先把一个目标编译出来再逐步加模块排查问题会方便很多。5.2 完整 CMakeLists.txt 编写cmake_minimum_required(VERSION 3.16) project(Demo VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(utility STATIC src/utility.cpp ) target_include_directories(utility PUBLIC include) add_executable(demo src/main.cpp) target_link_libraries(demo PRIVATE utility)相比前面的骨架这里多了两件关键的事把 utility 编译成静态库而不是直接把源文件编进主程序。使用 target_include_directories 把 include 目录作用在 utility 上并声明为 PUBLIC。这样 demo 在链接 utility 时include 路径会自动传递过来不需要再手动加一次。5.3 构建与运行假设在 Windows MinGW 搭配下mkdir build cd build cmake .. -G MinGW Makefiles -DCMAKE_BUILD_TYPERelease mingw32-make ./demo.exe在 Linux 下就简单多了mkdir build cd build cmake .. -DCMAKE_BUILD_TYPERelease make ./demo补充一个容易被忽略的点-DCMAKE_BUILD_TYPERelease。不设这个参数的话默认是空字符串很多编译器优化都没开程序性能会慢一大截而且你调试时还容易因为宏定义差异导致行为不同。这个参数只在单配置生成器下有意义比如 Makefile 和 NinjaVS 的多配置生成器不需要它而是通过 --config Release 在构建时指定。5.4 实操现场记录一次遗留问题排查我自己跑这个 demo 的时候出现过两个小问题写在这给大家参考。第一个是 CMake 报错。报错信息类似 CMake Error at CMakeLists.txt:5 (project)当时我以为是自己语法写错了反复检查都没发现问题。折腾很久才发现其实是本机 CMake 版本升级了而我 cmake_minimum_required 还写的 3.10一些新语法的解析方式不一样导致 project 那行的副作用异常。把版本要求提升到 3.16重新配置就正常了。第二个问题出现在 MinGW 下cmake .. -G MinGW Makefiles 之后报错找不到 mingw32-make。原因是 MinGW 的 bin 目录不在系统 PATH 里。当时我一直以为是 CMake 配置不识别实际上只要把 C:\msys64\mingw64\bin 这个路径加进 PATH再删掉 build 目录重新配置问题就解决了。这两个案例都属于看起来是 CMake 报错实际是环境问题的典型。我的经验是遇到莫名其妙的报错先别急着改代码先检查 PATH、CMake 版本、编译器架构这三样占了绝大多数隐性坑。6. 常见问题速查与避坑记录6.1 CMake 常见问题速查表问题现象常见原因解决方案找不到编译器编译器不在 PATH 或架构不匹配检查 PATH确认位数一致用 -DCMAKE_CXX_COMPILER 指定改了 CMakeLists.txt 不生效构建目录缓存删除 build 目录或移除 CMakeCache.txt 后重新配置第三方库 find_package 找不到库的 cmake 配置目录不在搜索路径把安装目录加入 CMAKE_PREFIX_PATHproject() 附近报错版本过旧触发语法兼容问题升级 CMake 或提升 cmake_minimum_requiredMinGW 构建失败mingw32-make 不在 PATH添加 MinGW bin 目录到 PATH清除缓存重新配置VSCode 状态栏没有 Configure 按钮CMake 不在 PATH 或未 Select a Kit安装 CMake 或指定 cmake.executable重新加载窗口链接失败但语法看起来没问题库顺序不对或缺少依赖检查 target_link_libraries 的顺序静态库之间的依赖要写明6.2 CMakeLists.txt 里加 strip 指令的正确姿势有人问cmake中添加strip指令strip 是发布二进制时很有用的操作但它不是 CMake 直接能做的得交给链接器。最简洁的方式是在构建完成后手动执行或者用 CMake 的自定义 targetadd_custom_target(strip_binary ALL COMMAND ${CMAKE_STRIP} $TARGET_FILE:demo DEPENDS demo )这里用到 ${CMAKE_STRIP}CMake 会根据当前工具链自动填好对应路径。这个自定义 target 的作用是每次构建 demo 完成后自动跑一次 strip把符号表去掉减小发布体积。我第一次用 add_custom_target 的时候没有写 DEPENDS结果构建和 strip 的执行顺序对不上一会儿成功一会儿失败把 DEPENDS demo 加上之后才稳定下来。6.3 Qt 和 CMake 的搭配注意点Qt 项目在 CMake 里有一层独有的处理过程通常写法是find_package(Qt6 COMPONENTS Widgets REQUIRED) qt_add_executable(myapp main.cpp MainWindow.cpp) target_link_libraries(myapp PRIVATE Qt6::Widgets)qt_add_executable 和 add_executable 的区别在于它会额外处理 Qt 的元对象编译器也就是 moc以及资源文件、UIC 文件等。写普通项目习惯用 add_executable但写 Qt 项目一定用 qt_add_executable否则很多槽函数和信号可能编译不过。搜索引擎里qt cmake这个关键词热度一直不低大概率都是在这块卡住了。另一个 Qt 相关的坑是版本一致性Qt6 的工程却用了 Qt5 的 find_package 写法CMake 在配置阶段就会直接版本不匹配。6.4 CMake 下载慢或版本不匹配的处理办法CMake 官方下载地址在某些网络环境下访问很慢这也是个老问题了。一个实用办法是通过 pip 安装pip install cmake安装后终端里直接有 cmake 命令版本通常比较新。要注意区分这个 pip 包不是源码而是可供调用的二进制它和 Python 生态没有代码层面的关联。版本不匹配的另一种常见表现是库要求 CMake 3.22但系统的 CMake 还是 3.10于是各种新命令都报错。我的建议是不要动系统自带的 cmake直接另装一个新版本到自定义路径构建时用完整路径启动或者用 shell 的 PATH 优先级来控制。这样既不影响老项目又能让新项目跑起来。6.5 几个新手常踩的坑每个都值得写进笔记只改 CMakeLists.txt 不重新配置。很多人在 Makefile 生成器下改了 CMakeLists但没重新跑 cmake结果整个构建系统还在用旧配置源码改了也不重新编译。重新 Configure 一下就好这个动作要多做。把所有源文件写在一行。CMake 允许你把源文件写成一个长字符串但可读性极差。一个文件一行git 冲突也容易解决。硬编码绝对路径。你机器上能用别人拿到项目根本跑不起来。尽量用 ${CMAKE_CURRENT_SOURCE_DIR}、${CMAKE_SOURCE_DIR} 这些变量拼接路径。不理解头文件搜索路径的来源。include_directories、target_include_directories 和系统路径三者混用最后你会分不清到底谁生效。统一用 target_ 系列命令每个模块的头文件来源都明确可见。我在实际使用中越来越觉得CMake 的入门曲线说难不难说简单也不简单但绝大多数人卡住的点其实不是语法而是配置和构建这两个阶段没有分清楚。只要理解了CMake 生成构建文件make 再调用编译器这个分工后面命令行报错、缓存问题、找不到编译器这些常见坑就都有了明确的排查方向。如果只能给出一条建议那就是永远在干净的 build 目录里做实验一旦发现配置混乱或行为诡异先删 cache 再重新配置。这套方法论我在 raylib、Eigen3、Qt、mosquitto 这些项目上都验证过屡试不爽。
返回列表