mthoutai

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

在这里插入图片描述

本篇摘要

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

一.CMakeLists相关命令解释

cmake_minimum_required 命令

  1. 核心作用

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

  2. 基本语法形式

    cmake_minimum_required(VERSION  [...] [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(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( [OPTIONAL] [RESULT_VARIABLE ])
  • <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++项目管理的关键技能。这些知识将显著提升开发效率和项目可维护性。

posted on 2025-10-28 15:24  mthoutai  阅读(9)  评论(0)    收藏  举报