资讯详情

CMake入门:三个核心命令从零构建第一个可执行程序

📅 2026/9/28 11:12:43 | 华诺云谱 👁 阅读
CMake入门:三个核心命令从零构建第一个可执行程序
你有没有过这种经历照着网上的教程手写了一份CMakeLists.txt结果在命令行执行cmake ..的时候报错报得怀疑人生要么找不到编译器要么莫名链接失败更糟的是在IDE里点一下就能跑的东西换个环境就完全不行。我见过不少写了好几年C的人一提到CMake就只说“复杂”“晦涩”其实真正用到的核心命令就那么几个。这篇文章就是冲着CMake最基础、最高频的三个命令来的cmake_minimum_required、project、add_executable。我会从环境安装、三个命令的逐个拆解到第一个可运行项目的完整实操再到常见报错排查把一套能直接“抄作业”的流程整理出来。无论你是刚接触C/C的学生还是被IDE保护得太好、想补上构建这一课的开发者跟着一步步走下来你对CMake的恐惧至少能消掉大半。1. 动手前的准备工作CMake环境怎么装、怎么验1.1 各平台下的安装方式与版本选择CMake本身是一个开源工具官方提供了Windows、Linux、macOS的安装包下载渠道非常直接去官网cmake.org的Download页面就能找到各平台的安装文件。我自己在不同操作系统上都配置过这里分享一下最实用的几种方式。Windows环境下最简单的是下载官方的Windows x64 Installer也就是常见的msi文件双击安装后勾选“Add CMake to the system PATH for all users”装完就能直接在cmd或PowerShell里用cmake命令。如果你偏好命令行安装也可以用wingetwinget install Kitware.CMake这个命令会自动下载安装并配置好PATH比手动安装更省事。安装完成后记得开一个新的终端窗口让环境变量生效。Linux上的安装方式取决于发行版。Debian/Ubuntu系列可以直接用aptsudo apt update sudo apt install cmake不过这里有个坑apt源里的CMake版本往往比较旧有时候已经落后两三个大版本。如果你用的CMakeLists.txt里写了cmake_minimum_required(VERSION 3.22)而apt给你的还是3.16那第一个报错就会出现在这里。遇到这种情况更推荐从官网下载预编译的Linux二进制包或者用Python的pip来安装pip install cmakepip提供的CMake版本更新很快而且会自动把可执行文件放进Python的Scripts目录里通常已经在了PATH中对开发环境干扰很小算是Linux下获取新版本CMake的一个捷径。macOS用户就没什么好纠结的Apple Silicon和Intel Mac都能直接用Homebrewbrew install cmake版本一般很新装完直接用。关于版本选择我的建议是能用新版就别守着老版本。CMake 3.16以上基本覆盖了近几年新增的绝大多数语法和特性3.20以上在使用体验上又顺畅了不少。最近CMake 4.x已经发布很多发行版也跟进到了4.2整体兼容性处理得还可以。保守一点的写法是cmake_minimum_required(VERSION 3.16)这个门槛不高绝大多数现存项目都能满足除非你有明确的新特性需求否则没必要在开头把版本门槛拉得太高因为那会让别人用老一点的环境直接编译失败。1.2 安装完成后怎么确认环境没问题装完之后别急着写CMakeLists先打开终端执行一条命令cmake --version正常输出类似cmake version 3.30.5 CMake suite maintained and supported by Kitware (kitware.com/cmake).看到版本号就说明安装成功了。接下来可以顺手看一下编译器和构建工具是否可用因为CMake本身只负责生成构建脚本真正编译还是要靠gcc、g、clang或者MSVC这些编译器。Windows上如果你装了Visual Studio要从“x64 Native Tools Command Prompt”进入或者确保VS的编译器在PATH中Linux/macOS直接用gcc/g或者clang即可。我习惯在做任何项目前先跑一遍这组命令把环境底子验清楚cmake --version gcc --version make --version这三条都正常后面的流程才会顺利。很多人报CMake找不到编译器其实是因为系统里压根没装gcc或者build-essential这类问题跟CMake本身一点关系都没有。2. 核心命令逐一拆解这三个命令到底在干什么2.1 cmake_minimum_required版本检查是第一道关卡这是CMakeLists.txt里必须要出现的第一行命令作用很直白声明这个项目要求的最低CMake版本。它的标准写法有两种cmake_minimum_required(VERSION 3.16)早期还允许不写VERSION关键字比如cmake_minimum_required(3.16)但新版CMake对写法已经收紧统一写成VERSION形式最稳妥。CMake在执行时会先读取这一行如果当前版本比你要求的低会直接报错并提示你升级CMake。为什么必须放在第一行因为CMakeLists.txt是自上而下逐行执行的后面用到的很多命令和特性都有版本门槛所以要先声明版本再做其他事情。举个例子add_executable从很老的低版本就存在但像target_sources(target PRIVATE a.cpp)这种写法是3.1才加入的如果不声明最低版本老版本会把你这个文件解析得一团糟而不是提前拦住你。cmake_minimum_required相当于一个前置检查器在环境不达标时提前暴露问题而不是让问题在深层语法解析或编译阶段才爆发。我写项目时一般遵循两个习惯一是最低版本定在3.16或3.20兼顾新特性和兼容性二是如果项目里有依赖的第三方库本身对CMake要求较高比如OpenCV新版要求3.10以上那么直接以库的要求为准不一定非要卡在最低处。2.2 project给项目起名、定语言还顺带定义一堆变量project()这个命令很多人理解成“起个名字而已”实际上它还承担了三件事项目名、支持的语言、以及项目版本号。最常用的写法project(MyFirstApp VERSION 1.0.0 LANGUAGES CXX)MyFirstApp是项目名通常和你要生成的可执行文件或库的名字保持一致。这个名字会出现在Visual Studio或Xcode生成的项目结构中也会用作一些变量的前缀比如PROJECT_NAME、PROJECT_VERSION等。VERSION指定项目版本元信息会写入变量方便在打包、发布或生成配置头文件时使用。LANGUAGES CXX明确告诉CMake只用C编译器。有些老教程不写这一项CMake默认启用C和CXX但为了明确意图建议显式声明。如果你的项目是纯C就写LANGUAGES C要是混合工程可以写LANGUAGES C CXX。project()执行之后CMake会自动生成一批以PROJECT_开头的变量比如PROJECT_SOURCE_DIR源码根目录和PROJECT_BINARY_DIR构建目录。这两个变量在复杂项目里会经常用到尤其是你要在CMakeLists里引用其他路径时不要用相对路径直接用这些变量去拼否则一旦换目录结构构建就会崩。这也是新手最容易踩的坑之一。2.3 add_executable把源文件变成可执行文件add_executable是CMake里“制造”可执行文件的核心命令正常用法是add_executable(MyFirstApp main.cpp)第一个参数是生成的可执行文件的目标名称第二个及以后的参数是源文件列表。目标名称通常不带.exe或.out后缀CMake会根据平台自行补全Windows上会生成MyFirstApp.exeLinux/macOS上生成MyFirstApp。执行这条命令之后CMake内部会创建一个“目标”target对象。这个概念是整个CMake的基石。目标本身不只是一个文件名字它还是一系列属性的载体——你可以给它添加宏定义、头文件搜索路径、链接的库甚至单独指定编译选项。这种“目标为中心”的设计让多文件、多库项目可以清晰地组织起来这也是CMake比裸写Makefile强大得多的地方。有个细节值得注意写add_executable的时候一定要把完整的源文件列表放进来如果少了文件编译的时候就会报类似“未定义的引用”“main函数找不到”的错误。解决方案就是你把它补进源文件列表里再加一句target_sources(MyFirstApp PRIVATE other.cpp)当然最开始学的时候还是建议老老实实把所有源文件一次性写全。我自己早期就吃过亏三四个cpp文件总是漏写后来养成了每新建一个源文件就同步更新CMakeLists的习惯就再也没犯过这类错误。关于main函数还有一个高频报错点如果你同时编译两个都有main函数的文件链接阶段会报main重复定义。原因很朴素一个可执行程序只能有一个入口点这跟CMake本身无关但很多新手会把错误归因到CMake头上于是到处删缓存最后才发现是两个文件撞了车。这类问题的排查经验后面我会专门整理。2.4 三个命令的执行顺序为什么不能乱CMakeLists.txt的执行是顺序的所以这三个命令的先后顺序是硬性规则cmake_minimum_required(VERSION x.x)必须在最前面。project(...)随后执行因为后面的命令已经需要依赖项目名、语言、变量了。add_executable(...)以及其他目标操作命令放在项目定义之后。如果你把project写在cmake_minimum_required前面CMake会直接拒绝执行报一个Implicit project() not allowed或不带PROJECT的错误。这个限制在新版本里尤其严格我见过不少人的报错日志里最后追踪到的问题居然只是顺序写反了非常可惜。一套完整的入门骨架就是下面这样cmake_minimum_required(VERSION 3.16) project(MyFirstApp VERSION 1.0.0 LANGUAGES CXX) add_executable(MyFirstApp main.cpp)熟悉这三行之后CMake对你就不再是黑盒了。3. 从零搭一个能跑的最小项目3.1 目录结构与CMakeLists写法我们按最小项目来目录结构就两个文件├── CMakeLists.txt └── main.cppmain.cpp就写一个最简单的打印#include iostream int main() { std::cout Hello, CMake! std::endl; return 0; }CMakeLists.txt就用上一节给出的三行骨架。这种极简结构明确了“什么文件做什么事”安装、编译、运行全流程跑通后再往复杂项目扩展心里才会有底。3.2 命令行构建从cmake到可执行文件老手和新手的一个显著差别是新手习惯直接在源码目录执行cmake .老手则一定会用独立的构建目录。推荐的流程是mkdir build cd build cmake .. cmake --build .这里每个步骤都是有讲究的。mkdir build建立了构建目录cmake ..让CMake在build文件夹内部生成构建脚本cmake --build .才是真正执行编译。为什么要绕这一圈因为CMake在配置阶段会产生大量中间文件比如CMakeCache.txt、各种日志文件和生成的Makefile如果全都堆在源码目录里源码目录会变得一片狼藉而且在切换配置Release/Debug、不同编译器时会发生严重的交叉污染。用独立构建目录你随时可以把build目录整个删掉重新来源码干干净净这种做法在业界叫out-of-source build是CMake官方推荐的标准用法。cmake --build .这条命令是跨平台的通用编译入口。在Windows的VS环境它会自动调用MSBuild在Linux/macOS上默认会调用make。你在任何CMake项目里都可以用它不用关心底层具体是什么构建工具。如果一次写了很多文件也可以给构建过程加个并行参数速度会快不少cmake --build . -j4-j后跟的数值是并行编译的任务数一般设置成CPU核心数或稍低太高手上内存可能扛不住。Linux/macOS下构建完成后直接运行生成的程序./MyFirstAppWindows下则是.\MyFirstApp.exe看到Hello, CMake!输出恭喜你第一个CMake项目已经跑通了。cmake-gui在这里也值得提一嘴。CMake自带图形界面Windows下可以通过开始菜单启动Linux下执行cmake-gui。你只需要在界面上设置源码目录和构建目录点击Configure选择生成器再点Generate生成构建脚本。它的作用和命令行cmake ..完全等价只是可视化了配置选项适合不喜欢敲命令的朋友。但要注意GUI底层的逻辑依然是配置生成生成之后还是要回到命令行或IDE里编译不是点一下Configure就能出可执行文件。3.3 在VSCode里用CMake Tools插件状态栏怎么出现Configure按钮很多人在VSCode里做C/C开发时会安装微软官方的CMake Tools插件这是目前最主流的CMake开发体验。但经常有人问安装之后底部状态栏为什么没有Configure按钮是插件没生效吗实际上CMake Tools插件的状态栏是“按需出现”的。只要VSCode打开了一个文件夹而且该文件夹里存在CMakeLists.txt插件就会自动识别出来。此时你打开任意一个CMakeLists.txt文件底部状态栏就会出现一组CMake相关的按钮从上到下依次是Kit选择、构建目标、编译按钮等等其中一个就是Configure。如果你打开的是一个C源文件状态栏可能不会显示这些按钮所以要把CMakeLists.txt激活为当前文件再观察。如果还没有出现直接按CtrlShiftP打开命令面板输入CMake: Configure手动触发配置流程。如果提示你没有选择Kit插件会弹出一个列表让你选择编译器套件比如Visual Studio、GCC或Clang选中之后再执行一次Configure状态栏基本就出来了。Configure按钮的含义就是执行一次CMake配置生成构建脚本正常执行成功后按钮上不会弹出明显的报错气泡。另外有一点容易被忽略CMake Tools插件在打开项目时会根据CMakeLists.txt和缓存状态自动判断要不要重新配置。如果你改了CMakeLists.txt插件通常会弹出提示让你重新Configure但它有时候会“迟钝”特别是改动的语法有细微问题的时候。我在改完CMakeLists后习惯手动触发一次Configure避免插件在自动模式下默默吞掉错误。3.4 顺手提一句这不是只能做本机程序有心人会发现CMake跨平台的能力不止于桌面环境。VSCode里用CMake开发STM32本质是给CMake指定一份交叉编译工具链文件toolchain file并把编译器换成arm-none-eabi-gcc这一套。核心逻辑依然是configure、build两步只是编译器来源发生了变化。类似地Android NDK工程里用CMake编译原生库也是同一个套路。理解了基础的三个命令再往上走就不虚。4. 高频报错排查这些错误我几乎每年都能遇到4.1 一份快速自查表下面这组组合是我在实际碰到的、各种群里问得最多的CMake入门报错整理成表格方便大家直接查。报错现象本质原因解决方案CMake Error: The source directory ... does not exist你指定的源码目录不对检查cmake ..中..是否真的指向了含CMakeLists.txt的目录建议先pwd确认Could not find a package configuration file provided by Qt5find_package找不到第三方库的配置文件用CMAKE_PREFIX_PATH指定库的安装路径比如cmake .. -DCMAKE_PREFIX_PATHE:/Qt/5.9.4/msvc2017_64/usr/share/cmake-4.2/modules/CMakeDetermineCompilerId.cmake:9附近报错CMake探测编译器时环境有问题检查编译器和依赖环境比如gcc是否安装、PATH是否正确再看CMakeError.log里的细节main被重复定义两个源文件都写了main函数检查源文件列表只能保留一个入口undefined reference链接阶段缺符号检查是否漏链接库或漏加源文件配置成功了但没生成可执行文件只执行了cmake没执行编译必须跑cmake --build .Policy CMP... is not set项目用到新特性但CMake版本偏低升级CMake或在文件里手动设定策略优先选择升级版本4.2 第三方库找不到九成是CMAKE_PREFIX_PATH没配对很多热词里都有“Qt5Config.cmake”找不到这类报错比如这种CMake Error at C:/Qt/Qt5.9.4/5.9.4/msvc2017_64/lib/cmake/Qt5/Qt5Config.cmake:9看到这种消息第一反应应该是find_package(Qt5 ...)这一句没找到对应的配置文件。CMake搜索第三方库时会遵循一套规则它会去哪里找一是默认的系统路径二是CMAKE_PREFIX_PATH指定的路径。Qt库安装路径往往不是系统默认搜索路径所以就会在一个比较靠前的环节直接失败。解决思路是给CMake指明前缀路径。Windows上Qt安装目录通常类似C:/Qt/5.9.4/msvc2017_64那我们就在配置阶段追加参数cmake .. -DCMAKE_PREFIX_PATHC:/Qt/5.9.4/msvc2017_64如果你的依赖是OpenCV同样可以用-DCMAKE_PREFIX_PATH指向OpenCV安装目录这里的原理完全一致。还有一个常用排查技巧-DCMAKE_PREFIX_PATH可以配置多个路径用分号分隔比如cmake .. -DCMAKE_PREFIX_PATHC:/Qt/5.9.4/msvc2017_64;C:/opencv/4.8.0学会这一招以后遇到任何“Could not find a package”的报错你都有一条明确的排查思路。4.3 CMakeError.log才是排错真正的第一手现场很多人在CMake报错后就跑来问人直接把终端最后几行贴出来。终端信息确实有用但它往往是笼统的结论真正的详细日志躺在构建目录里。以/usr/share/cmake-4.2/modules/CMakeDetermineCompilerId.cmake:9这类错误为例它只告诉你在CMake内置脚本的某一行出了问题具体编译器为什么探测失败要看build/CMakeFiles/CMakeError.log或CMakeOutput.log。我的习惯是一旦配置阶段报错先去构建目录找这两个日志文件。CMakeError.log会记录编译器尝试编译测试代码时的完整错误输出里面往往有具体的编译器路径、链接命令和系统错误信息。很多时候配置失败是因为编译器版本过高、环境变量没配对或者缺少某个系统库这些在日志里都能看到线索。另外缓存文件CMakeCache.txt里记录了上次配置的所有重要变量包括编译器路径、构建类型、前缀路径等。如果你怀疑某个变量没有生效去这个文件里搜索它看到它被赋值成什么心里就有数了。如果实在找不到头绪最粗暴也最有效的办法是删掉build目录重新配置。我做CMake相关排错时至少有三分之一的情况是直接clean build解决的。不要心疼那几十秒的配置时间干净重来往往能消除大量隐藏变量污染。4.4 分清是谁家的报错Gradle和CMake不是一个物种热词里有一条很典型的报错A problem occurred configuring root project lark-android. could not dete...。很多人把这个也归到CMake头上但实际上这是Gradle的报错。Gradle在配置原生项目时会在背后调用CMake报错前缀是Gradle的真正的根因还是要看CMake层面的日志。Android原生开发经常用到NDK加CMakeGradle负责构建整个Android应用CMake只是被Gradle当成一个编译原生代码的工具。如果你在AS或命令行里看到could not determine之类的字样排查顺序应该是先看Gradle的输出信息里是否引用了CMake的错误文件再定位到具体路径然后去对应的build/intermediates或CMake错误日志里找CMake本身给出的原因。凡是这类“一层套一层”的构建报错最怕的就是在错误的层面瞎猜先分清是谁在报错再按它自己的日志去查。4.5 编译配置阶段的几个关键“为什么”还有一个很常见的场景就是刚跑完cmake ..却发现什么事情都没发生或者根本没有生成可执行文件。原因多半在于你只完成了“配置”还没开始“构建”。CMake和传统的Makefile还真不太一样打个比方cmake命令相当于根据你提交的源文件清单生成一份订单cmake --build .才真正让工人按订单生产可执行文件。所以配置成功不等于构建成功这两步必须配合着走。与此相关的是生成器选择。同样是配置不同环境下生成的“工厂”不一样Windows上默认可能是Visual Studio解决方案Linux上是Makefile如果装了Ninja还可以用-G Ninja指定这个并行度极高的构建工具。Ninja值得一试尤其是大项目构建速度往往比默认的Makefile快不少。你可以直接这么配置cmake -G Ninja ..前提是系统里装了Ninja二进制。用它生成的构建目录会多出build.ninja文件这就是Ninja的构建脚本。5. 从三个命令走向真实项目多源文件与依赖管理5.1 加了新源文件别用“捡漏式”写法当项目从单个main.cpp扩展到多个源文件时最直观的做法是把所有文件列到add_executable里add_executable(MyFirstApp main.cpp utils.cpp logger.cpp )这是最稳妥、最不容易出错的写法。多文件顺序无所谓CMake在编译时会自动处理依赖关系。重要的是不要用file(GLOB ...)来“自动收集”源文件因为GLOB是在配置阶段展开的如果你往目录里新增了.cpp文件而没有重新运行cmake配置它很可能不会自动识别新文件甚至Z在IDE里会出现“明明文件在目录里却编译不到”的诡异问题。我建议新文件就手动加进去麻烦一次清爽无数。那种“文件太多不想逐个写”的心情我很理解但CMake社区对这个写法的口径非常统一明确列出文件不要依赖文件系统自动扫描。除了可维护性还有一个原因是构建系统需要确定哪些文件参与编译写在CMakeLists里的列表天然就是明确的声明。5.2 链接第三方库从OpenCV装到find_package热词里反复出现“OpenCV cmake编译步骤”这类搜索词说明很多人第一步就是倒腾OpenCV这种重量级依赖。理论上OpenCV提供的CMake配置文件已经做得很完整我们只需要在CMakeLists里find_package(OpenCV REQUIRED) target_link_libraries(MyFirstApp PRIVATE ${OpenCV_LIBS})find_package会去系统路径和CMAKE_PREFIX_PATH里搜索OpenCV的配置文件一旦找到就会定义OpenCV_LIBS这个变量里面是编译好的OpenCV库文件路径。target_link_libraries再把它们链接到你的可执行目标上。用CMake处理第三方通用库的流程基本就是这个套路find_package找到库的配置文件target_link_libraries建立链接关系如果还有头文件路径需要指定用target_include_directories补上。这套“目标属性”机制让每个编译目标都能精确声明自己需要什么、链接什么互相之间不会串味这是CMake在大型项目里能够长期稳定工作的根本原因。5.3 为什么我们选择CMake而不是直接写Makefile既然热词里有人问“makefile和cmake的区别”我这里也帮忙理清一下。Makefile是直接面向构建工具的脚本语言写起来的一行就是一条构建规则而CMake是一种“生成器语言”它根据CMakeLists.txt生成对应平台的Makefile、Ninja文件或者Visual Studio工程。用生活类比的话Makefile有点像手写菜谱每个菜目标的原材料、步骤都要你自己写清楚CMake则更像你告诉中央厨房“我要一份番茄炒蛋”平台自动帮你把菜谱生成了。CMake的跨平台能力、目标系统、依赖管理能力都是Makefile难以匹敌的这也是为什么现代C/C项目几乎都往CMake迁移。CMake还有一个好处就是你写的CMakeLists在Windows和Linux上保持一致不用维护两套构建脚本这对团队协作来说价值极大。当然CMake内部也有策略机制不同版本对某些写法的默认行为可能不同一旦项目跨版本使用就会遇到策略警告。但入门阶段不用担心这个保持旧版默认行为就可以了。先把那三行“地基”打稳后面所有复杂功能都是在这个基础上升级出来的。结尾与个人经验最后说点我个人的实在体会。我带过的不少新人最开始都不太愿意“浪费时间”学CMake觉得“反正IDE能跑”。但一旦项目换到云服务器、CI流水线或者需要跨平台编译IDE的光环瞬间就失效了构建这套东西还是得回到命令行、回到CMakeLists本身。花一个下午把这套基本功练扎实后面遇到的多文件、多库、交叉编译问题就都有了主线思路。再分享一个小技巧没事的时候多去完整地读一遍CMake跑通时的输出日志。很多人只看结果“成功了”从不看它到底做了什么。CMake输出的每一条信息都有价值编译器路径、构建类型、库搜索路径全在里面。有时候看不懂不是因为你笨而是没把日志当阅读理解材料真把一两份完整日志啃下来再回看各种报错时你会有种“原来如此”的通透感。如果要从这篇文章里带走三句话那就是把cmake_minimum_required放第一行用project定义项目元信息用add_executable把源文件变成可执行文件配置和构建一定要在独立目录里用cmake --build .收口报错别慌先找缓存、日志和CMakeCache再决定要不要删build重来。把这三句话刻进肌肉记忆CMake就不再是拦路虎了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑