CMake 核心概念与实战指南
CMake 是 C++ 项目的构建工具,通过目标、属性和 API 管理项目。本文详解 cmake_minimum_required、project、include、install、add_executable 等命令,讲解动静态库依赖及现代 CMake 工作流程,帮助开发者掌握工程化构建技能。

CMake 是 C++ 项目的构建工具,通过目标、属性和 API 管理项目。本文详解 cmake_minimum_required、project、include、install、add_executable 等命令,讲解动静态库依赖及现代 CMake 工作流程,帮助开发者掌握工程化构建技能。

CMakeLists.txt 文件的第一行。基本语法形式
cmake_minimum_required(VERSION <min> [...<policy_max>] [FATAL_ERROR])
其中 VERSION 是关键字,后面必须跟版本号。
<min>:最重要的参数,用于指定一个最低版本号(例如 3.18)。如果当前运行的 CMake 版本低于此版本,配置将失败。<policy_max> (可选):用于指定一个最高策略版本,此选项较少使用。FATAL_ERROR (可选):显式指定版本不满足时报错并终止流程。对于 CMake 2.6 及以后的版本,这是默认行为,因此现代项目中通常无需再写。CMakeLists.txt 文件的开头位置(通常在 cmake_minimum_required 之后),也可以把它(project_name)理解成命名空间。project() 后,CMake 会自动创建一系列变量供后续使用,例如:完整语法(支持可选参数)
project(MyProject VERSION 1.0.0 DESCRIPTION "A great project" HOMEPAGE_URL "https://example.com" LANGUAGES C CXX
可以可选地指定项目的版本号、描述信息、主页链接和编程语言(如 C, C++)。
基本语法
project(<PROJECT-NAME>)
最简单的形式,只需提供一个项目名称(如 project(MyAwesomeApp))。
PROJECT_NAME: 项目名称PROJECT_VERSION: 项目的完整版本号PROJECT_SOURCE_DIR: 项目源代码的根目录路径PROJECT_BINARY_DIR: 项目构建的输出目录路径一句话: project() 是 CMake 项目的'身份证',用它来定义项目最基本也是最重要的元信息。
常见变量使用:
PROJECT_NAME 变量的使用场景:
PROJECT_VERSION 变量的使用场景:
下面了解下 version 与 languages 这俩字段:
这里可以用 message 函数进行打印调试的。
这里要知道默认的 languages 是 C 与 CXX 的;如果确定哪个语言就只写那个,否则就会都生成耗时间;这里一旦写错就会出现问题(比如把 CXX 程序改成 C):
.cmake 脚本)或模块中的 CMake 代码。基本语法
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>])
<file|module>:要包含的文件或模块名。OPTIONAL:可选关键字。如果找不到文件,不会报错,静默忽略。RESULT_VARIABLE <var>:可选关键字。将查找结果(找到的文件完整路径)存入指定变量 <var> 中;如果未找到且使用了 OPTIONAL,则该变量值为 NOTFOUND。<module>.cmake 的文件:CMAKE_MODULE_PATH 变量指定的目录列表中查找。include 的文件中的 CMake 代码会在当前上下文中立即执行,就像直接把那段代码粘贴到 include 的位置一样。include 执行外部文件时,会改变以下CURRENT 系列变量的值,以反映正在执行的文件信息:CMAKE_CURRENT_LIST_FILE:变为被包含文件的完整路径。CMAKE_CURRENT_LIST_DIR:变为被包含文件所在的目录路径。CMAKE_CURRENT_SOURCE_DIR 和 CMAKE_CURRENT_BINARY_DIR:保持不变,仍然是包含者(父 CMakeLists.txt)的源目录和构建目录。一句话: include 用于将外部 CMake 代码文件插入当前执行流程,并会更新当前文件(CURRENT_LIST_*)相关的上下文变量。
常见的就是可以利用 include 包含对应 cmake 脚本;可以用那三个对应变量查看当前执行的 cmake 文件及目录等等。
cp 命令一样,把编译好的程序、库文件、头文件、配置文件复制(部署)到指定的系统目录中。发布给他人使用 (高级)
install(EXPORT ...)
用于打包自己的库并发布,方便其他项目直接引用。
指定安装位置
DESTINATION <路径>
指定文件要复制到哪。可以是绝对路径(如 /usr/local),也可以是相对路径(相对于 CMAKE_INSTALL_PREFIX 这个变量)。
安装整个目录
install(DIRECTORY 目录/ DESTINATION 目标路径)
用来安装整个文件夹及其里面的所有内容。
安装单个文件
install(FILES 文件.h DESTINATION include)
用来安装单个的头文件或配置文件。
安装程序/库 (最常用)
install(TARGETS 目标名 DESTINATION bin/lib)
用来安装由 add_executable 或 add_library 生成的可执行文件或库文件。
步骤可以总结成三步:
install 命令,记住要安装哪些文件。cmake_install.cmake)。make install 时,CMake 就运行这个脚本,把文件复制到指定位置。一句话: install 就是定义在运行 make install 时,要把哪些文件拷贝到哪个目录下的规则。
下面演示下常用的方法:
这里需要注意的是默认是安装在 /usr/local 目录里;我们可以执行相对和绝对目录(相对就是.开始即可;绝对就是/开头)。
可以看到对应的默认目录就是 /usr/local。
可以看到为 make install 生成安装可执行程序的目录都被记载到 cmake_install.cmake;后续直接就调用这个脚本进行安装就行。
成功安装在绝对目录。
这里最后也会生成对应的文件;方便后续 cpack 执行的方便。
.exe)的核心命令。基本语法形式
add_executable(target_name source1.cpp source2.cpp ...)
target_name:必需。指定生成的可执行文件的名称(不含扩展名,如 myapp)。此名称在项目内部必须唯一。source1.cpp ...:必需。指定构建此可执行文件所需的源文件列表(如 main.cpp, helper.cpp)。CMakeLists.txt 源目录对应的构建目录(build tree)中。RUNTIME_OUTPUT_DIRECTORY 属性来更改其默认的输出目录。一句话总结: add_executable 是告诉 CMake '请用这些源代码(sources)帮我编译一个名叫 target_name 的程序'。
下面演示下给它设置属性来完成自定义目录安装(make install):
把对应的 main 程序的生成目录改到 /exe;而 test 还是在对应 build 目录不变。
对应 exe 目录无文件。
cmake 后进行 make 生成对应可执行程序;符合预期。
静态库:
动态库:
总之,就是动态库使用的时候需要链接(可达共享);而静态库直接就是归档文件,填进程序即可,无需链接操作。
下面基于顶层 CMakeLists.txt 以及底层 CMakeLists.txt 实现对应的程序与静态库编成可执行程序进行执行:
对应开始目录:
├── CMakeLists.txt
├── myapp
│ ├── CMakeLists.txt
│ └── main.cpp
└── my_lib
├── CMakeLists.txt
├── include
│ └── math.h
└── src
├── add.cpp
└── sub.cpp
这里我们把对应的 src 里的 cpp 文件以及包含对应 include 里面的 math.h 组成对应的静态库(头文件声明 + 源文件实现)。
这里编写对应的 main.cpp 出现了问题无法识别对应;因为它在固定的目录找不到 math.h;因此可以考虑给它加进 include 路径内。
对应的静态库生成的 CMakeLists.txt 文件:
主函数结合静态库最终生成对应的执行程序的 CMakeLists.txt 文件:
顶层 CMakeLists.txt 文件只需要调用前两个底层即可:
下面进行运行:
然后构建对应的程序及静态库的生成。
发现生成的位置有点不一样。
下面和原本那两个底层 CMakeLists.txt 位置对比看下:
生成的程序及库的相对位置都是基于对应的那两个底层 CMakeLists.txt 的相对位置目录来依据生成的。
如果想要指定对应目录到指定的 build 目录里面;因此就可以调整生成的可执行程序的属性即可:
那两个底层 CMakeLists.txt 进行添加:
如果不指定是顶层 cmake 对应生成的目标目录;它会相对当前的 cmakeLists 目录为相对目录生成对应那些文件;比如此时 cmake.txt 相对的是 my_lib 这个目录;因为它是被顶层 cmake 调用的;因此会生成在 build 文件;但是又要保证与 my_lib 在同一目录;因此会生成在对应 build 的 my_lib 目录里。
下面干掉 build;重新生成下(一定要删掉对应顶层 cmake 的 cmakecache):
成功生成在指定目录了。
成功运行。
现代 CMake 的核心是'目标(Target)'为中心的管理模式。每个库或可执行程序都是一个独立的目标,拥有自己的属性和行为。
add_library 或一个程序 add_executable)。target_include_directories, target_link_libraries)。| 关键字 | 对当前目标的构建影响 | 是否传播给下游目标? | 对下游目标(使用者)的影响 | 通俗解释 | 生活化举例(面包与面粉) |
|---|---|---|---|---|---|
| PRIVATE | 生效 | 否 | 不生效 | 私有属性:只给自己用,不告诉别人。 | 面粉品牌:面包房自己知道用什么面粉,但包装上不写,顾客看不到。 |
| PUBLIC | 生效 | 是 | 生效 | 公共属性:自己要用,也告诉别人要用。 | 公开配方:面包房用特定品牌面粉,并在包装上写明,顾客也知道。 |
| INTERFACE | 不生效 | 是 | 生效 | 接口属性:自己不用,但要求别人用。 | 产品说明书:一个不生产面包的机构,发布一个标准,规定做面包必须用某种面粉。 |
PUBLIC、PRIVATE、INTERFACE 关键字,一个目标的属性(如头文件路径、依赖库)可以自动、精确地传递给依赖它的其他目标,从而避免手动管理全局变量带来的混乱和错误。一句话: 现代 CMake 倡导像管理对象一样管理构建目标,通过清晰的属性和自动的依赖传递机制,来构建高效、可靠且易于维护的项目。
CMake 中的'目标'就是你想让构建系统帮你生成的东西,主要分三大类:
| 目标类型 | 干啥用的? | 一句话概括 |
|---|---|---|
| EXECUTABLE | 生成可执行程序 | 最终能直接运行的程序(如 main.exe, curl) |
| 各种 LIBRARY (STATIC, SHARED, MODULE) | 生成库文件 | 供其他程序调用的代码包(如 .a, .so, .dll) |
| 特殊目标 (INTERFACE, IMPORTED, ALIAS) | 不生成文件,用于管理 | 定义规则、引用外部库或给目标取别名 |
核心思想: 你用 add_executable 或 add_library 告诉 CMake '我要生成什么',CMake 就会帮你搞定编译和链接过程。
| 属性类别 | 管什么? | 一句话概括 |
|---|---|---|
| 全局属性 (Global) | 整个项目(从开始到结束) | 项目级的全局设置 |
| 目录属性 (Directory) | 当前文件夹及子文件夹 | 文件夹级的统一规则 |
| 目标属性 (Target) | 单个程序或库(最常用) | 每个组件的个性设置 |
| 源文件属性 (Source) | 单个代码文件 | 单个文件的特殊处理 |
| 测试属性 (Test) | 单个测试用例 | 控制每个测试怎么跑 |
| 安装属性 (Install) | 要安装的文件 | 规定软件装到哪怎么装 |
核心思想: CMake 让你能从 '整个项目' 到 '单个文件' 的不同粒度,层层递进地精细控制构建过程。目标属性是其中最核心、最常用的一类。
可以把 CMake 想象成一个乐高玩具的管理员,这些 API 就是管理员不同阶段的工作:
set_target_properties):管理员的小本本:用来记录或查看某块乐高(目标)的所有属性(比如颜色、大小)。target_compile_definitions):拼装说明书:告诉管理员某块乐高该怎么拼(比如用什么工具、按什么步骤)。target_link_libraries):拼接规则:告诉管理员哪些乐高块可以拼在一起,以及拼好后放哪。install):包装入盒:规定拼好的成品如何打包进盒子(安装),方便送给别人(分发)。一句话: 这四类 API 从 '记录属性 -> 如何制作 -> 如何组装 -> 如何打包',完整覆盖了一个软件从代码变成可分发的成品的全过程。
CMakeLists.txt 脚本,登记所有要构建的目标(如可执行文件、库),并记录下您设置的各种属性(如头文件路径、编译选项)。Makefile 或 build.ninja,比如链接库什么的 -I -L 等都会转化成这样的指令进 makefile)。makefile 调用编译器 (gcc/clang) 和链接器 (ld),根据生成的脚本执行编译和链接,将源代码转换为最终的二进制程序或库。cmake_install.cmake),用于将构建好的程序和相关文件复制到系统的安装目录中。全一句话:
CMake 就是一个自动化项目经理:它先理解您的构建需求(配置),然后制定详细的施工计划(生成),接着指挥工人干活(构建),最后负责产品的打包和部署(安装)。
本篇通过学习 CMake 三大核心(目标、属性、API)和完整构建流程(配置、生成、构建、安装),将掌握现代化 C++ 项目管理的关键技能。这些知识将显著提升开发效率和项目可维护性。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online