【CMake】 工程实战:可执行文件从编译、链接到安装全流程深度拆解


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言
  • [一. CMake 可执行文件构建的全流程原理](#一. CMake 可执行文件构建的全流程原理)
  • [二. 核心命令逐行深度解析](#二. 核心命令逐行深度解析)
    • [2.1 cmake_minimum_required:CMake 版本门槛控制](#2.1 cmake_minimum_required:CMake 版本门槛控制)
    • [2.2 project:项目信息全局定义](#2.2 project:项目信息全局定义)
    • [2.3 include:模块与脚本加载](#2.3 include:模块与脚本加载)
    • [2.4 install:构建产物安装部署](#2.4 install:构建产物安装部署)
    • [2.5 add_executable:可执行文件目标定义](#2.5 add_executable:可执行文件目标定义)
  • 三、完整实战:从零搭建可执行文件工程
    • [3.1 工程目录结构](#3.1 工程目录结构)
    • [3.2 编写 C++ 源代码](#3.2 编写 C++ 源代码)
    • [3.3 编写 CMakeLists.txt(逐行解析)](#3.3 编写 CMakeLists.txt(逐行解析))
    • [3.4 工程构建与安装全流程执行](#3.4 工程构建与安装全流程执行)
  • 四、常见踩坑指南与最佳实践
    • [4.1 高频报错与解决方案](#4.1 高频报错与解决方案)
    • [4.2 工程化最佳实践](#4.2 工程化最佳实践)

前言

C/C++ 开发中,工程构建一直是新手入门的第一道坎:Linux 下手写 Makefile 语法晦涩、跨平台适配成本极高,Windows 下 Visual Studio 的工程配置无法直接迁移到 Linux/macOS,不同 IDE 的构建体系各自为战,导致项目跨环境编译处处碰壁。而 CMake 作为 C/C++ 领域事实上的工程构建标准 ,凭借一处配置、到处构建 的核心优势,完美解决了跨平台构建的痛点,也是所有商业级 C/C++ 项目的首选构建工具。本文将从零拆解可执行文件从编译、链接到系统安装的全流程,深度解析每一个核心 CMake 命令的语法、底层逻辑与最佳实践,配合完整可运行的工程代码,让你彻底吃透 CMake 可执行文件构建的核心玩法。


一. CMake 可执行文件构建的全流程原理

一个完整的 CMake 可执行文件构建与安装流程,分为 4 个核心阶段,每个阶段的职责与执行逻辑完全解耦,这也是 CMake 跨平台能力的核心支撑:

阶段 核心动作 关键产出
配置阶段(Configure) 执行 cmake 命令,解析 CMakeLists.txt 语法,检查编译器、依赖环境,校验 CMake 版本兼容性 构建环境校验报告、CMakeCache.txt 缓存文件
生成阶段(Generate) 完成配置后,根据目标平台生成对应的构建系统文件 Linux 生成 Makefile、Windows 生成 VS 工程文件、macOS 生成 Xcode 配置
编译链接阶段(Build) 执行构建命令,调用编译器完成源文件编译、目标文件生成,最终链接为可执行文件 二进制可执行文件、中间目标文件 (.o/.obj)
安装阶段(Install) 将编译好的可执行文件、配套资源复制到系统标准路径,处理权限与运行时路径 全局可调用的程序、标准化的目录部署

最佳实践:全程使用源外构建(独立 build 目录),所有构建产物与源代码完全分离,避免污染源码目录;禁止使用源内构建(直接在源码目录执行 cmake .),否则会导致源码目录充斥大量临时文件,难以清理。



二. 核心命令逐行深度解析

CMake 可执行文件的构建与安装,核心依赖 5 个基础命令,它们构成了 CMake 工程的最小闭环,也是所有大型 CMake 项目的基石。本文将结合官方语法与实战场景,逐行拆解每个命令的用法与底层逻辑。

2.1 cmake_minimum_required:CMake 版本门槛控制

函数作用

指定项目所需的最低 CMake 版本,必须放在顶级 CMakeLists.txt 的第一行,且在 project () 命令之前调用。该命令会在配置阶段检查当前 CMake 版本,若低于要求则直接终止并报错,避免因版本不兼容导致的语法错误、行为异常等模糊问题。

基本语法

cpp 复制代码
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])

参数详解

参数 核心含义
VERSION 固定关键字,标识后续内容为版本号
项目所需的最低 CMake 版本,本文所有案例最低要求为 3.18
FATAL_ERROR 可选参数,CMake 2.6 + 默认开启,版本不满足时直接终止配置

关键注意事项

  1. 执行顺序强制要求:必须在顶级 CMakeLists.txt 的第一行调用,且先于 project (),否则 CMake 会触发开发警告;

  2. 版本适配参考:不同 Linux 发行版预装的 CMake 版本不同,选择最低版本时需兼顾兼容性与新特性,参考对照表如下:

    Linux 发行版 系统版本 预装 CMake 版本
    Ubuntu LTS 22.04 (Jammy) 3.22
    Ubuntu LTS 24.04 (Noble) 3.28
    Debian 12 (Bookworm) 3.22
    CentOS Stream 9 3.22+
  3. 禁止在函数内调用:函数内调用会限制作用域,无法全局生效,属于官方不推荐的用法。

实战报错案例

若本地 CMake 版本为 3.28.3,而项目设置了更高的版本要求:

cpp 复制代码
cmake_minimum_required(VERSION 3.30)

执行 cmake 命令时会直接终止,输出明确报错:

bash 复制代码
CMake Error at CMakeLists.txt:2 (cmake_minimum_required):
CMake 3.30 or higher is required. You are running version 3.28.3


2.2 project:项目信息全局定义

函数作用

指定项目名称与核心元信息,放在顶级 CMakeLists.txt 的第二行,子目录中一般无需重复调用。该命令执行后,CMake 会自动生成一系列全局内置变量,供后续配置逻辑使用。

基本语法

cpp 复制代码
# 最简形式
project(<PROJECT-NAME>)

# 完整形式
project(<PROJECT-NAME>
    [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
    [DESCRIPTION <project-description-string>]
    [HOMEPAGE_URL <url-string>]
    [LANGUAGES <language-name>...]
)

参数详解

参数 核心含义
项目名称,全局唯一,会自动赋值给PROJECT_NAME内置变量
VERSION 可选,项目版本号,格式为主版本.次版本.修订号,会自动拆解为多个内置变量
LANGUAGES 可选,项目支持的编程语言,C/C++ 项目常用C CXX;不指定时默认启用 C 和 CXX

自动生成的内置变量

project () 执行完成后,CMake 会自动创建以下全局变量,可直接通过${变量名}调用:

变量名 变量描述
PROJECT_NAME 项目名称,与完全一致
CMAKE_PROJECT_NAME 顶级项目名称,与 PROJECT_NAME 一致
PROJECT_VERSION 完整的项目版本号,如 1.2.3
PROJECT_VERSION_MAJOR 主版本号,如 1
PROJECT_VERSION_MINOR 次版本号,如 2
PROJECT_VERSION_PATCH 修订号,如 3
PROJECT_SOURCE_DIR 顶级 CMakeLists.txt 所在的源码根目录
PROJECT_BINARY_DIR 项目构建目录,即 build 目录

关键注意事项

  1. 强制直接调用 :顶级 CMakeLists.txt 必须直接调用 project (),通过 include 加载的间接调用无效,否则 CMake 会发出警告,并默认生成project(Project)兜底;
  2. 语言参数避坑 :若仅设置LANGUAGES C,但源文件为.cpp 后缀,会触发Cannot determine link language for target报错,需根据源码类型正确指定编程语言;
  3. 版本号典型场景:常用于调试打印、生成包配置文件、动态库版本号管理,是工程化版本管控的核心。


  • VERSION详解

  • languages参数详解






2.3 include:模块与脚本加载

函数作用

加载指定的脚本文件或 CMake 模块,到当前 CMakeLists.txt 的执行上下文中并运行,是 CMake 复用工具函数、引入标准模块的核心方式。

基本语法

cpp 复制代码
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>] [NO_POLICY_SCOPE])


参数详解

参数 核心含义
<file|module> 要加载的 cmake 文件路径,或 CMake 内置模块名称
OPTIONAL 可选,若文件不存在不会触发报错

核心执行逻辑

  1. 路径搜索规则
    1. 相对路径:相对于当前执行的 CMakeLists.txt 所在目录;
    2. 绝对路径:直接从对应路径读取文件并执行;
    3. 模块搜索:先查找当前目录,再查找CMAKE_MODULE_PATH变量指定的目录,最后查找 CMake 内置模块目录。
  2. 作用域规则 :被包含的文件会在当前上下文直接执行,不会创建新的作用域,可直接访问和修改当前作用域的所有变量。
  3. 内置变量变化:这是 include 与 add_subdirectory 最核心的区别,如下表所示:
变量名 执行父 CMakeLists.txt 时 执行 include 的子 cmake 文件时 变化说明
CMAKE_CURRENT_SOURCE_DIR 父目录路径 父目录路径 不变化,执行上下文始终为父目录
CMAKE_CURRENT_LIST_FILE 父 CMakeLists.txt 全路径 子 cmake 文件全路径 变化,指向当前正在执行的文件
CMAKE_CURRENT_LIST_DIR 父 CMakeLists.txt 所在目录 子 cmake 文件所在目录 变化,指向当前执行文件的目录

实战案例

我们通过一个极简工程验证 include 的变量行为: 目录结构

Plain 复制代码
test_include
├── CMakeLists.txt
└── sub
    └── sub.cmake

顶层 CMakeLists.txt

cpp 复制代码
cmake_minimum_required(VERSION 3.18)
project(TestInclude)

# 打印父目录上下文的内置变量
message(STATUS "from top-level CMakeLists.txt")
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}")
message(STATUS "CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")

# 包含子目录的cmake脚本
include(sub/sub.cmake)

sub/sub.cmake

cpp 复制代码
message(STATUS "from sub/sub.cmake")
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}")
message(STATUS "CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")

执行输出

Plain 复制代码
-- from top-level CMakeLists.txt
-- CMAKE_CURRENT_SOURCE_DIR: /home/bit/test_include
-- CMAKE_CURRENT_LIST_FILE: /home/bit/test_include/CMakeLists.txt
-- CMAKE_CURRENT_LIST_DIR: /home/bit/test_include
-- from sub/sub.cmake
-- CMAKE_CURRENT_SOURCE_DIR: /home/bit/test_include
-- CMAKE_CURRENT_LIST_FILE: /home/bit/test_include/sub/sub.cmake
-- CMAKE_CURRENT_LIST_DIR: /home/bit/test_include/sub

2.4 install:构建产物安装部署

函数作用

将编译好的可执行文件、库、头文件等产物部署到指定目录,可简单理解为跨平台的 cp 命令,同时自动处理文件权限、运行时路径、目录规范等细节,是程序发布、打包的前置核心步骤。

基本语法

可执行文件安装最常用的是TARGETS形式,语法如下:

cpp 复制代码
install(TARGETS <targets>...
    [EXPORT <export-name>]
    [RUNTIME DESTINATION <dir>]
    [LIBRARY DESTINATION <dir>]
    [ARCHIVE DESTINATION <dir>]
    [INCLUDES DESTINATION <dir>]
)

参数详解

参数 核心含义
TARGETS 要安装的目标,即 add_executable/add_library 定义的目标名
DESTINATION 安装路径,可使用绝对路径,或相对路径(相对于CMAKE_INSTALL_PREFIX
RUNTIME 目标类型,可执行文件属于 RUNTIME 类别

核心规则与最佳实践

  1. 默认安装前缀 :Linux/macOS 下CMAKE_INSTALL_PREFIX默认值为/usr/local,Windows 下默认为C:/Program Files
  2. GNU 目录规范 :通过include(GNUInstallDirs)引入 CMake 内置的 GNU 安装目录模块,可执行文件默认安装到${CMAKE_INSTALL_PREFIX}/bin,遵循行业标准目录规范,禁止硬编码安装路径;
  3. 安装阶段的核心价值
    1. 分离开发树与安装树,仅保留对外发布的最小产物集;
    2. 跨平台统一文件复制、权限设置逻辑,屏蔽 Windows/Linux/macOS 的路径差异;
    3. 生成导出配置文件,供其他 CMake 项目通过 find_package 引用;
    4. 为 CPack 打包工具提供文件清单与路径规则。


2.5 add_executable:可执行文件目标定义

函数作用

指示 CMake 从指定源代码生成一个可执行文件,是构建可执行程序的核心命令。在 CMake 中,可执行文件是三大核心目标(可执行文件、静态库、动态库)之一,所有编译、链接、安装规则都围绕目标展开。

基本语法

cpp 复制代码
add_executable(<target_name> 
    [WIN32] [MACOSX_BUNDLE]
    [EXCLUDE_FROM_ALL]
    [source1] [source2 ...]
)


参数详解

参数 核心含义
target_name 可执行文件名称,项目内必须全局唯一,不包含平台相关的扩展名
<sources> 源文件列表,如 main.cpp、test.c 等,用于编译生成可执行文件
WIN32 Windows 平台专用,生成窗口程序而非控制台程序
MACOSX_BUNDLE macOS 平台专用,生成.app 应用 bundle

关键注意事项

  1. 目标名唯一性:同一个 CMake 工程中,所有目标(可执行文件、库)的名称不能重复;
  2. 输出路径控制 :默认在与 CMakeLists.txt 对应的构建目录中生成可执行文件,可通过RUNTIME_OUTPUT_DIRECTORY目标属性修改输出路径;
  3. 避坑提示 :若源文件列表为空、或源文件后缀不被 CMake 识别,会触发无法确定链接语言的报错,需补充正确的可编译源文件。

三、完整实战:从零搭建可执行文件工程

本节我们将基于上述核心命令,搭建一个完整的 CMake 可执行文件工程,完成从代码编写、编译链接、到系统全局安装的全流程,所有代码均可直接复制运行。

3.1 工程目录结构

我们采用标准的源外构建结构,目录树如下:

Plain 复制代码
install_hello
├── CMakeLists.txt  # 顶层CMake配置文件,工程核心
├── main.cpp        # C++程序源代码
└── build           # 构建目录,所有构建产物均存放于此

3.2 编写 C++ 源代码

新建main.cpp文件,编写最简 C++ 入口程序:

cpp 复制代码
#include <iostream>

int main() 
{
    // 控制台输出内容,验证程序运行
    std::cout << "Hello CMake!" << std::endl;
    return 0;
}

3.3 编写 CMakeLists.txt(逐行解析)

新建CMakeLists.txt文件,这是 CMake 工程的核心配置文件,完整代码如下,我们将逐行拆解每一行的作用:

cpp 复制代码
# ======================
# 1. 设置CMake最低版本要求
# ======================
# 要求CMake版本至少为3.18,低于该版本直接终止配置
cmake_minimum_required(VERSION 3.18)

# ======================
# 2. 定义项目全局信息
# ======================
# 项目名称为InstallHello,版本1.2.3,支持C和C++语言
project(InstallHello
    VERSION 1.2.3
    LANGUAGES C CXX
)

# ======================
# 3. 定义可执行文件构建目标
# ======================
# 生成名为hello的可执行文件,源文件为main.cpp
add_executable(hello main.cpp)

# ======================
# 4. 引入GNU安装目录规范
# ======================
# 加载CMake内置的GNUInstallDirs模块,定义标准化安装路径
include(GNUInstallDirs)

# ======================
# 5. 配置可执行文件安装规则
# ======================
# 将hello可执行文件安装到系统标准路径
install(TARGETS hello)

# ======================
# 调试打印:内置变量输出
# ======================
# 输出项目名称、版本号、安装路径等信息,用于调试验证
message(STATUS "PROJECT_NAME: ${PROJECT_NAME}")
message(STATUS "PROJECT_VERSION: ${PROJECT_VERSION}")
message(STATUS "PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}")
message(STATUS "PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}")
message(STATUS "PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}")
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "CMAKE_INSTALL_BINDIR: ${CMAKE_INSTALL_BINDIR}")

3.4 工程构建与安装全流程执行

以下所有命令均在 Linux 终端执行,Windows/macOS 仅需替换对应编译器,CMake 命令完全通用。

Step 1:创建构建目录并进入

shell 复制代码
# 进入工程根目录,执行以下命令
mkdir build && cd build

核心目的:实现源外构建,所有构建产物均放在 build 目录,与源代码完全分离。

Step 2:执行 CMake 配置与生成

shell 复制代码
# 解析上级目录的CMakeLists.txt,生成Makefile构建文件
cmake ../

执行输出核心片段

Plain 复制代码
-- The C compiler identification is GNU 13.3.0
-- The CXX compiler identification is GNU 13.3.0
-- Detecting C compiler ABI info - done
-- Detecting CXX compiler ABI info - done
-- PROJECT_NAME: InstallHello
-- PROJECT_VERSION: 1.2.3
-- PROJECT_VERSION_MAJOR: 1
-- PROJECT_VERSION_MINOR: 2
-- PROJECT_VERSION_PATCH: 3
-- CMAKE_INSTALL_PREFIX: /usr/local
-- CMAKE_INSTALL_BINDIR: bin
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/bit/install_hello/build

配置成功标志:输出Configuring doneGenerating done,build 目录下已生成 Makefile 文件。

Step 3:编译链接生成可执行文件

shell 复制代码
# 执行构建,编译链接生成可执行文件
cmake --build .
# 也可使用make命令,效果完全一致

执行输出

Plain 复制代码
[ 50%] Building CXX object CMakeFiles/hello.dir/main.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello

构建完成后,build 目录下已生成名为hello的可执行文件。

Step 4:本地运行验证

shell 复制代码
# 执行生成的可执行文件
./hello

输出结果

Plain 复制代码
Hello CMake!

验证程序编译链接成功,功能运行正常。

Step 5:安装到系统全局路径

shell 复制代码
# 安装到系统目录需要管理员权限,添加sudo
sudo cmake --install .

执行输出

Plain 复制代码
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/bin/hello

CMake 已将 hello 可执行文件复制到/usr/local/bin目录,该目录默认在系统 PATH 中,支持全局调用。

Step 6:全局运行验证

shell 复制代码
# 退出build目录,在任意路径执行hello命令
cd ~
hello

输出结果

Plain 复制代码
Hello CMake!

至此,我们完成了 CMake 可执行文件从编写、构建到系统安装的全流程闭环。


四、常见踩坑指南与最佳实践

4.1 高频报错与解决方案

报错信息 根因分析 解决方案
CMake X.X or higher is required. You are running version X.X cmake_minimum_required 设置的版本高于本地安装的 CMake 版本 降低项目最低版本要求,或通过官方渠道升级本地 CMake
Cannot determine link language for target "xxx" 1. 目标无有效可编译源文件;2. 源文件后缀不识别;3. project 未启用对应编程语言 补充正确的.c/.cpp 源文件,在 project 中添加对应的 LANGUAGES 参数
Permission denied 安装时报错 安装到系统目录需要管理员权限 执行 sudo cmake --install .,或通过 - DCMAKE_INSTALL_PREFIX 修改安装路径到用户目录
VS Code 中 #include 头文件标红 VS Code 的 C/C++ 插件未配置正确的 includePath 鼠标点击标红处,通过 Quick Fix 自动添加 includePath,或手动修改.vscode/c_cpp_properties.json 配置

4.2 工程化最佳实践

  1. 强制源外构建:全程使用独立 build 目录,禁止在源码目录直接执行 cmake,保持源码目录干净整洁;
  2. 严格执行命令顺序:cmake_minimum_required 必须放在第一行,project 放在第二行,避免 CMake 触发警告与非预期行为;
  3. 显式指定编程语言:在 project 中明确指定项目用到的 LANGUAGES,避免 CMake 执行无用的编译器检测;
  4. 遵循 GNU 目录规范:安装时必须引入 GNUInstallDirs 模块,禁止硬编码安装路径,保证跨平台兼容性;
  5. 跨平台构建命令 :优先使用cmake --build .执行编译,而非直接 make/nmake,该命令可跨平台自动适配对应构建工具;
  6. 目标名全局唯一:可执行文件、库的目标名在项目内必须唯一,避免出现链接冲突与覆盖问题。

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:本文我们从零拆解了 CMake 可执行文件从编译、链接到安装的全流程,深度解析了cmake_minimum_requiredprojectincludeadd_executableinstall五大核心命令的语法与底层逻辑,通过完整的实战工程完成了从代码编写到系统全局安装的全流程落地,同时总结了开发中高频踩坑点与商业级项目最佳实践。可执行文件的构建与安装是 CMake 工程化的基石,后续我们将基于这个基础,继续深入 CMake 静态库 / 动态库的构建与引用、第三方库的查找与集成、大型项目的目录结构管理、测试与打包发布等进阶内容。CMake 的核心魅力在于目标 - 属性的现代化管理模式,掌握了基础的可执行文件目标构建,就打开了 CMake 工程化的大门。建议大家动手完成本文的实战案例,亲手执行每一条命令,才能真正吃透 CMake 的核心逻辑。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
正点原子1 小时前
【正点原子Linux连载】 第五章 字符设备驱动开发 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南
linux·运维·驱动开发
路由侠内网穿透1 小时前
本地部署开源 HTTP 服务器 OpenLiteSpeed 并实现外部访问
运维·服务器·网络·网络协议·http·开源
ppandss11 小时前
JavaWeb从0到1-DAY7-HTTP 请求与响应处理
网络·网络协议·http
云水一下1 小时前
下一代防火墙(NGFW)完全解析:从入门到华为eNSP模拟器实战
网络·华为·下一代防火墙
源远流长jerry1 小时前
Linux内核之一条tcp到底占用多少内存
linux·运维·服务器·网络·网络协议·tcp/ip
肖坤超1 小时前
Ubuntu 26.04 完美安装和设置
linux·运维·ubuntu
Agent手记1 小时前
成品发货全流程自动化,落地实操与错发漏发规避方案 | 2026企业级Agent端到端落地指南
运维·人工智能·ai·自动化
杂家1 小时前
Docker 容器端口无法从外部访问
运维·服务器·docker·容器
骄傲的心别枯萎1 小时前
WireShark抓取rtsp包
网络·测试工具·wireshark