在这里插入图片描述

本篇摘要

CMake是C++项目的构建神器。本文详解其核心概念:目标、属性和API,手把手教你编写CMakeLists.txt,实现项目构建、测试和打包全流程。掌握现代CMake的最佳实践,提升工程化能力。

一.CMakeLists相关命令解释

cmake_minimum_required 命令

  1. 核心作用

    用于指定项目所需的最低 CMake 版本。这是一个强制性的要求,必须放在项目顶层 CMakeLists.txt 文件的第一行

  2. 基本语法形式

    cmake_minimum_required(VERSION <min> [...<policy_max>] [FATAL_ERROR])
    

    其中 VERSION 是关键字,后面必须跟版本号。

  3. 关键参数解释

  • <min>:最重要的参数,用于指定一个最低版本号(例如 3.18)。如果当前运行的 CMake 版本低于此版本,配置将失败。
  • <policy_max> (可选):用于指定一个最高策略版本,此选项较少使用。
  • FATAL_ERROR (可选):显式指定版本不满足时报错并终止流程。对于 CMake 2.6 及以后的版本,这是默认行为,因此现代项目中通常无需再写。
  1. 版本号设置参考

    设置版本号时需考虑不同操作系统和发行版的软件源中预装的 CMake 版本。例如:

  • Ubuntu 22.04:约 v3.22
  • Ubuntu 24.04:约 v3.28
  • Debian 12:约 v3.22
  • Fedora / Arch:版本通常较新
  1. 为何必须设置

    确保项目能在符合要求的构建环境中正确配置,避免因开发者本地 CMake 版本过旧或过新(导致策略行为差异)而引发不可预见的错误,保证项目构建的一致性和可重现性

这里也可以跳转查看对应官网介绍:

在这里插入图片描述
在这里插入图片描述

演示下具体操作:

在这里插入图片描述

  • 这里看到安装的cmake的版本;下面规定txt中比它版本号高。

在这里插入图片描述
在这里插入图片描述

  • 这里就会爆出错误并及时终止cmake继续运行。

在这里插入图片描述

在这里插入图片描述

  • 也可以这样写给它规定一个版本的范围。

project命令

  1. 核心作用

    用于指定项目的名称,是 CMake 项目的核心标识。此命令必须放在顶级 CMakeLists.txt 文件的开头位置(通常在 cmake_minimum_required 之后),也可以把它(project_name)理解成命名空间。

  2. 基本语法

    project(<PROJECT-NAME>)
    

    最简单的形式,只需提供一个项目名称(如 project(MyAwesomeApp))。

  3. 完整语法(支持可选参数)
    如:

    project(MyProject
        VERSION 1.0.0
        DESCRIPTION "A great project"
        HOMEPAGE_URL "https://example.com"
        LANGUAGES C CXX
    

    可以可选地指定项目的版本号描述信息主页链接编程语言(如 C, C++)。

  4. 自动创建变量

    执行 project() 后,CMake 会自动创建一系列变量供后续使用,例如:

  • PROJECT_NAME: 项目名称
  • PROJECT_VERSION: 项目的完整版本号
  • PROJECT_SOURCE_DIR: 项目源代码的根目录路径
  • PROJECT_BINARY_DIR: 项目构建的输出目录路径
  1. 主要用途
  • 作为项目的唯一标识。
  • 用于自动生成库文件名、配置文件名等。
  • 管理项目的版本信息,便于后续打包和分发。
  • 定义项目源代码和构建结果的路径。

一句话: project() 是 CMake 项目的“身份证”,用它来定义项目最基本也是最重要的元信息。

常见变量使用:

PROJECT_NAME 变量的使用场景:

  1. 动态库的输出名称
  2. cmake 配置文件的名称
  3. 命名空间的名称

PROJECT_VERSION 变量的使用场景:

  1. 打印变量
  2. 生成 pkg - config 或者.cmake 对应的版本配置文件
  3. 动态库/静态库的版本号

下面了解下version与languages这俩字段:

这里可以是用message函数进行打印调试的。

在这里插入图片描述

  • 下面规定下版本及语言;查看下打印信息。

在这里插入图片描述

  • 这里可以看到关于安装c++等相关文件配置等;对应版本号显示;以及源项目 和编写的cmake 文件目录;cmake对应生成的目录;以及项目二进制生成的目录等;后面因为没有开启install故只显示了路径前缀。

这里要知道默认project的languages是C与CXX的;如果确定哪个语言就只写那个,否则就会都生成耗时间;这里一旦写错就会出现问题(比如把CXX程序改成C):

在这里插入图片描述

  • 这里可以看到找不到对应的C++编译工具等。

include命令

  1. 核心作用

    用于在当前 CMakeLists.txt 的上下文中加载并执行另一个文件(.cmake 脚本)或模块中的 CMake 代码。

  2. 基本语法

    include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>])
    
  • <file|module>:要包含的文件或模块名。
  • OPTIONAL:可选关键字。如果找不到文件,不会报错,静默忽略。
  • RESULT_VARIABLE <var>:可选关键字。将查找结果(找到的文件完整路径)存入指定变量 <var> 中;如果未找到且使用了 OPTIONAL,则该变量值为 NOTFOUND
  1. 文件搜索路径
  • 如果指定的是相对路径,则从当前正在执行的 CMakeLists.txt 所在目录开始查找。
  • 如果指定的是绝对路径,则直接加载该路径下的文件。
  • 先相对找不到就绝对。
  1. 模块 (Module) 搜索路径

    当参数不是路径格式时,CMake 会将其视为模块名,并按以下顺序搜索名为 <module>.cmake 的文件:

  • 首先在当前目录查找。
  • 然后在 CMAKE_MODULE_PATH 变量指定的目录列表中查找。
  1. 执行逻辑

    include 的文件中的 CMake 代码会在当前上下文中立即执行,就像直接把那段代码粘贴到 include 的位置一样。

  2. 关键变量行为(重要)

    include 执行外部文件时,会改变以下CURRENT系列变量的值,以反映正在执行的文件信息:

  • CMAKE_CURRENT_LIST_FILE:变为被包含文件的完整路径。
  • CMAKE_CURRENT_LIST_DIR:变为被包含文件所在的目录路径。
  • CMAKE_CURRENT_SOURCE_DIRCMAKE_CURRENT_BINARY_DIR保持不变,仍然是包含者(父 CMakeLists.txt)的源目录和构建目录。

一句话: include 用于将外部 CMake 代码文件插入当前执行流程,并会更新当前文件(CURRENT_LIST_*)相关的上下文变量。

常见的就是可以利用include包含对应cmake脚本;可以用那三个对应变量查看当前执行的cmake文件及目录等等;下面演示下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 为了区别执行对应命令所在的文件以及目录;这里搞个新的目录来存储对应脚本。

在这里插入图片描述

  • 发现这里除了对应当前执行源文件目录是同的;对应的执行当前命令的文件和目录都是不同的。

install 命令

  1. 核心作用

    cp 命令一样,把编译好的程序、库文件、头文件、配置文件复制(部署)到指定的系统目录中。

  2. 安装程序/库 (最常用)

    install(TARGETS 目标名 DESTINATION bin/lib)
    

    用来安装由 add_executableadd_library 生成的可执行文件或库文件。

  3. 安装单个文件

    install(FILES 文件.h DESTINATION include)
    

    用来安装单个的头文件或配置文件。

  4. 安装整个目录

    install(DIRECTORY 目录/ DESTINATION 目标路径)
    

    用来安装整个文件夹及其里面的所有内容。

  5. 指定安装位置

    DESTINATION <路径>
    

    指定文件要复制到哪。可以是绝对路径(如 /usr/local),也可以是相对路径(相对于 CMAKE_INSTALL_PREFIX 这个变量)。

  6. 发布给他人使用 (高级)

    install(EXPORT ...)
    

    用于打包自己的库并发布,方便其他项目直接引用。

步骤可以总结成三步:

  1. 收集:CMake 读取你的 install 命令,记住要安装哪些文件。
  2. 生成:CMake 把这些指令写成一个自动安装脚本 (cmake_install.cmake)。
  3. 执行:当你运行 make install 时,CMake 就运行这个脚本,把文件复制到指定位置。

一句话: install 就是定义在运行 make install 时,要把哪些文件拷贝到哪个目录下的规则。

下面演示下常用的方法:

这里需要注意的是默认是安装在/usr/local目录里;我们可以执行相对和绝对目录(相对就是.开始即可;绝对就是/开头)。

在这里插入图片描述
在这里插入图片描述

  • 可以看到对应的默认目录就是/usr/local

在这里插入图片描述
在这里插入图片描述

  • 可以看到为make install 生成安装可执行程序的目录都被记载到stall.cmake;后续直接就调用这个脚本进行安装就行。

在这里插入图片描述

  • 成功安装在绝对目录。

在这里插入图片描述

  • 这里最后也会生成对应的文件;方便后续cpack执行的方便。

add_executable 命令

  1. 核心作用

    用于指示 CMake 从源代码生成一个可执行文件。这是创建可执行程序(如 .exe)的核心命令。

  2. 基本语法形式

    add_executable(target_name source1.cpp source2.cpp ...)
    
  3. 关键参数

  • target_name必需。指定生成的可执行文件的名称(不含扩展名,如 myapp)。此名称在项目内部必须唯一
  • source1.cpp ...必需。指定构建此可执行文件所需的源文件列表(如 main.cpp, helper.cpp)。
  1. 输出位置
  • 默认位置:可执行文件会生成在与 CMakeLists.txt 源目录对应的构建目录build tree)中。
  • 自定义位置:可以通过设置目标的 RUNTIME_OUTPUT_DIRECTORY 属性来更改其默认的输出目录。

一句话总结: add_executable 是告诉 CMake “请用这些源代码(sources)帮我编译一个名叫 target_name 的程序”。

下面演示下给它设置属性来完成自定义目录安装(make install):

在这里插入图片描述

  • 把对应的main程序的生成目录改到/exe;而test还是在对应build目录不变。

在这里插入图片描述

  • 对应exe目录无文件。

在这里插入图片描述

  • cmake后进行make生成对应可执行程序;符合预期。

二.基于CMake组织实现静态库依赖程序运行

重温动静态库

静态库:

  • 直接塞进程序里,编译完就能单独运行。
  • 优点:省心,不用管用户电脑有没有库。
  • 缺点:程序会变大。

动态库:

  • 程序运行时才临时调用,需要用户电脑上有这个库文件。
  • 优点:程序小,多个程序能共用同一个库。
  • 缺点:分发麻烦,少个库程序就崩。

总之,就是动态库使用的时候需要链接(可达共享);而静态库直接就是归档文件,填进程序即可,无需链接操作。

下面基于顶层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;因此可以考虑给它加进json串内。

在这里插入图片描述

在这里插入图片描述

  • 这里就找到了。

对应的静态库生成的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的三大核心:目标 属性 API介绍

概念介绍

现代 CMake 的核心是 “目标(Target)” 为中心的管理模式。每个库或可执行程序都是一个独立的目标**,拥有自己的属性和行为。**

  1. 三大核心概念
  • 目标 (Target):构建系统的基本单元(如一个库 add_library 或一个程序 add_executable)。
  • 属性 (Properties):每个目标所具有的特征或元数据(如包含路径、编译选项、链接库等)。
  • API:用于操作目标及其属性的命令接口(如 target_include_directories, target_link_libraries)。
  1. 关键机制:属性传递
关键字 对当前目标的构建影响 是否传播给下游目标? 对下游目标(使用者)的影响 通俗解释 生活化举例(面包与面粉)
PRIVATE 生效 不生效 私有属性:只给自己用,不告诉别人。 面粉品牌:面包房自己知道用什么面粉,但包装上不写,顾客看不到。
PUBLIC 生效 生效 公共属性:自己要用,也告诉别人要用。 公开配方:面包房用特定品牌面粉,并在包装上写明,顾客也知道。
INTERFACE 不生效 生效 接口属性:自己不用,但要求别人用。 产品说明书:一个不生产面包的机构,发布一个标准,规定做面包必须用某种面粉。
  • 这是现代 CMake 最强大的特性。通过 PUBLICPRIVATEINTERFACE 关键字,一个目标的属性(如头文件路径、依赖库)可以自动、精确地传递给依赖它的其他目标,从而避免手动管理全局变量带来的混乱和错误。
  1. 与面向对象编程的类比
  • 目标 (Target)类 (Class)
  • 属性 (Properties)成员属性
  • API 命令成员函数
  1. 最终等式

    目标 + 属性 + API + 属性传递机制 = CMake 现代化构建系统的核心

一句话: 现代 CMake 倡导像管理对象一样管理构建目标,通过清晰的属性和自动的依赖传递机制,来构建高效、可靠且易于维护的项目。

目标

CMake 中的“目标”就是你想让构建系统帮你生成的东西,主要分三大类:

目标类型 干啥用的? 一句话概括
EXECUTABLE 生成可执行程序 最终能直接运行的程序(如 main.exe, curl
各种 LIBRARY (STATIC, SHARED, MODULE) 生成库文件 供其他程序调用的代码包(如 .a, .so, .dll
特殊目标 (INTERFACE, IMPORTED, ALIAS) 不生成文件,用于管理 定义规则、引用外部库或给目标取别名

核心思想: 你用 add_executableadd_library 告诉 CMake “我要生成什么”,CMake 就会帮你搞定编译和链接过程。

属性

属性类别 管什么? 一句话概括
全局属性 (Global) 整个项目(从开始到结束) 项目级的全局设置
目录属性 (Directory) 当前文件夹及子文件夹 文件夹级的统一规则
目标属性 (Target) 单个程序或库(最常用) 每个组件的个性设置
源文件属性 (Source) 单个代码文件 单个文件的特殊处理
测试属性 (Test) 单个测试用例 控制每个测试怎么跑
安装属性 (Install) 要安装的文件 规定软件装到哪怎么装

核心思想: CMake 让你能从 “整个项目”“单个文件” 的不同粒度,层层递进地精细控制构建过程。目标属性是其中最核心、最常用的一类。

API

可以把 CMake 想象成一个乐高玩具的管理员,这些 API 就是管理员不同阶段的工作:

  1. 通用读写 (set_target_properties)

    管理员的小本本:用来记录或查看某块乐高(目标)的所有属性(比如颜色、大小)。

  2. 编译阶段 (target_compile_definitions)

    拼装说明书:告诉管理员某块乐高该怎么拼(比如用什么工具、按什么步骤)。

  3. 链接与输出 (target_link_libraries)

    拼接规则:告诉管理员哪些乐高块可以拼在一起,以及拼好后放哪。

  4. 安装与打包 (install)

    包装入盒:规定拼好的成品如何打包进盒子(安装),方便送给别人(分发)。

一句话:
这四类 API 从 “记录属性 -> 如何制作 -> 如何组装 -> 如何打包”,完整覆盖了一个软件从代码变成可分发的成品的全过程。

cmake三大核心工作流程:

  1. 配置期:CMake 读取您的 CMakeLists.txt 脚本,登记所有要构建的目标(如可执行文件、库),并记录下您设置的各种属性(如头文件路径、编译选项)。
  2. 生成期:CMake 将上一步登记的目标和属性翻译或具体化 成底层构建工具(如 MakeNinja)能直接执行的脚本文件(如 Makefilebuild.ninja,比如链接库什么的 -I -L 等都会转化成这样的指令进makefile)。
  3. 构建期:通过makefile调用编译器 (gcc/clang) 和链接器 (ld),根据生成的脚本执行编译和链接,将源代码转换为最终的二进制程序或库。
  4. 安装期利用配置期设置的属性,生成一个安装脚本 (cmake_install.cmake),用于将构建好的程序和相关文件复制到系统的安装目录中。

全一句话:

CMake 就是一个自动化项目经理:它先理解您的构建需求(配置),然后制定详细的施工计划(生成),接着指挥工人干活(构建),最后负责产品的打包和部署(安装)。

四.本篇小结

本篇通过学习CMake三大核心(目标、属性、API)和完整构建流程(配置、生成、构建、安装),您已掌握现代化C++项目管理的关键技能。这些知识将显著提升开发效率和项目可维护性。

Logo

欢迎加入我们的广州开发者社区,与优秀的开发者共同成长!

更多推荐