【Cmake】cmake_minimum_required,project,include,install,add_executable
目录
一.可执⾏⽂件(编译-链接-安装)
1.1.cmake_minimum_required函数
1.2.project函数
1.2.1.project函数的使用注意事项
1.2.2.project生成的变量
1.2.3.project_name参数相关的变量
1.2.4.version参数相关的变量
1.2.5.language参数相关的变量
1.3.include函数
1.3.1.示例
1.3.2.CMake进⼊⼦⽬录之后,内置路径变量的变化情况
1.4.install函数
1.5.add_executable函数
一.可执⾏⽂件(编译-链接-安装)
本例⼦我们会编译和链接⼀个最简单的输出hellobits的C++程序可执⾏程序,并本地运⾏,然后 安装到系统⽬录下。过程中会介绍⼀些遇到的新的CMake函数。
main.cpp
#include <iostream>int main()
{std::cout << "hello Bits!" << std::endl;return 0;
}
CMakeLits.txt
# 1 设置最低cmake 版本
# 指定构建此项目所需的最低CMake版本为3.18
# 如果当前CMake版本低于此要求,会报错并停止构建
cmake_minimum_required(VERSION 3.18)# 2 设置项目名称
# 定义项目名称为"InstallHello",并设置项目版本号为1.2.3
# LANGUAGES参数指定项目使用的编程语言为C和C++
project(InstallHelloVERSION 1.2.3LANGUAGES C CXX
)# 3 添加构建目标
# 创建一个名为"hello"的可执行文件构建目标
# 该可执行文件的源文件是main.cpp
add_executable(hello main.cpp)# 4 安装到本地
# 包含GNUInstallDirs模块,该模块提供了标准安装目录的定义
# 如CMAKE_INSTALL_BINDIR(二进制文件安装目录)等
include(GNUInstallDirs)# 安装指令:将构建目标hello安装到默认位置
# 如果没有指定安装目录,可执行文件默认安装到CMAKE_INSTALL_BINDIR目录
install(TARGETS hello)
1.1.cmake_minimum_required函数
如何去官方文档寻找cmake_minimum_required
cmake官网:CMake - Upgrade Your Software Build System
我这里告诉大家怎么去官网寻找这些函数
在这里输入我们需要寻找的函数即可
点击自己想要的那个即可
cmake_minimum_required函数
cmake_minimum_required 命令用于指定项目所需的最低 CMake 版本,应当放置在顶级 CMakeLists.txt 文件的第一行。其基本语法形式为:
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])
cmake_minimum_required
是 CMake 的第一个关键命令,它有两个核心作用:
-
版本门禁:设定一个底线,拒绝过旧的、无法正确构建你项目的 CMake 版本。
-
策略管理:控制 CMake 自身的行为,确保构建脚本在不同版本下的行为一致性和可预测性。
1. VERSION
关键字
-
作用:这是一个固定的关键字,用于引入后面跟随的版本号参数。它必须写上。
2. <min>
(最低版本)
-
作用:指定你的项目必须使用的最低 CMake 版本。
-
格式:通常是
主版本号.次版本号
(例如3.10
),也可以是更详细的主版本号.次版本号.补丁号
(例如3.18.4
)。 -
行为:
-
如果当前运行的 CMake 版本高于或等于
<min>
,配置继续。 -
如果当前运行的 CMake 版本低于
<min>
,CMake 会立即报错并停止整个配置过程。
-
-
为什么重要:CMake 在不断更新,每个版本都会引入新的命令、变量和特性。如果你在脚本中使用了某个新特性(例如
target_sources()
在 3.1 才加入),但却用旧版本(如 2.8)去运行,结果将是无法预测的错误。<min>
确保了构建环境满足脚本的基本要求。 -
示例:
VERSION 3.10
表示你的项目至少需要 CMake 3.10 才能构建。
3. [...<policy_max>]
(可选策略版本)
-
作用:这是一个高级功能,用于精细地控制 CMake 的策略(Policy) 行为,旨在消除不同小版本 CMake 之间的行为差异。
-
格式:在
<min>
后面用...
连接另一个版本号<policy_max>
(例如3.16...3.20
)。 -
行为(根据当前 CMake 版本不同):
-
当前版本 <
<min>
:报错停止(与只指定<min>
时相同)。 -
当前版本 >=
<min>
且 <<policy_max>
:CMake 会启用所有在<policy_max>
版本中被设置为NEW
(新行为)的策略。这强制当前版本模仿<policy_max>
版本的行为。 -
当前版本 >=
<policy_max>
:此参数无效,直接使用该版本默认的最新行为。
-
-
为什么重要:CMake 的版本迭代中,有些旧行为被认为是有问题的或过时的。为了保持向后兼容,CMake 引入了“策略”机制。每个策略都对应一个特定行为的改变(例如
CMP0077
是关于option()
命令的行为)。新版本默认使用新行为(NEW
),但旧行为(OLD
)仍然可用。使用...<policy_max>
可以让你声明:“我的项目是按照<policy_max>
版本的新行为来编写的,请所有低于此版本的 CMake 都按这个标准来执行”,从而保证行为一致性。 -
示例:
VERSION 3.16...3.20
-
你用 CMake 3.28 构建:忽略
3.20
,直接使用 3.28 的行为。 -
你用 CMake 3.18 构建:CMake 会检查所有在 3.20 中默认启用的策略,并在 3.18 中也启用它们,从而让 3.18 的行为无限接近 3.20。
-
你用 CMake 3.14 构建:报错,因为 3.14 < 3.16 (
<min>
)。
-
4. [FATAL_ERROR]
(可选错误选项)
-
作用:在现代 CMake(2.6 及以上版本)中,此选项已被忽略且无任何效果。 它的存在纯粹是为了向后兼容。
-
历史原因:在古老的 CMake 2.4 及更早版本中,
cmake_minimum_required
不会报错,只会发出警告。添加FATAL_ERROR
是为了让 2.4 及以下的版本也能因此报错并停止,而不是继续构建一个几乎注定会失败的项目。 -
建议:虽然现在它没什么用,但作为一种良好的习惯和明确的意图表达,通常还是会把它写上。
注意事项
需要注意的是,cmake_minimum_required
命令在 CMake 项目中具有特殊的基础性地位,其调用位置和方式需要遵循明确的规范:
1.必须位于顶级CMakeLists.txt
文件的首行
该命令必须作为顶级 CMakeLists.txt
文件中的第一条命令出现。这是因为 CMake 在解析过程中会立即处理版本要求和策略设置,这些设置将直接影响后续所有命令的解释和执行方式。将其置于首位可以确保整个项目的配置过程都在正确的版本环境和策略规则下进行。
2. 必须在 project 命令之前调用
务必在 project
命令之前调用 cmake_minimum_required
。project
命令本身的行为也会受到 CMake 版本和策略设置的显著影响,例如定义项目名称、版本号、启用语言标准等操作都与特定版本的特性相关。先确立版本要求,再定义项目,可以保证项目初始化过程的准确性和一致性。
3.强烈不建议在函数内部调用
虽然技术上可以在函数体内调用 cmake_minimum_required
,但这种做法具有明显的局限性且通常不被推荐,主要原因包括:
-
作用域限制:在函数中调用时,命令设置的
CMAKE_MINIMUM_REQUIRED_VERSION
等变量仅存在于函数局部作用域,不会向上传递到调用者的作用域。这会导致项目其他部分无法感知到版本要求,可能引发意外的兼容性问题。 -
策略行为的矛盾性:虽然函数内部不引入新的策略作用域(意味着策略设置会影响调用者),但这种混合了影响与不影响调用作用域的特性,会使得项目的策略管理变得复杂和难以预测,破坏了策略设置本应带来的全局一致性。
-
违背设计意图:该命令的本意是为整个项目设定一个全局的、统一的基础环境。将其隐藏在某个函数中会大大降低项目配置的清晰度和可维护性,其他协作者难以一眼看出项目的基础版本要求。
这里有几个非常简单的例子来描述 cmake_minimum_required
的用法和作用。
例子 1:最基本用法
这是最常见和推荐的做法,放在 CMakeLists.txt
文件的最开头。
# 要求 CMake 的最低版本为 3.16
cmake_minimum_required(VERSION 3.16)project(MyApp)
add_executable(MyApp main.cpp)
-
作用:如果用户试图用低于 3.16 版本的 CMake(比如 3.10)来构建这个项目,CMake 会立即报错并停止,提示版本不满足要求。这保证了你的脚本中使用的所有特性(这些特性可能在 3.16 中才引入)不会在旧版本上导致不可预知的错误。
例子 2:版本过低时的错误展示
假设你的 CMakeLists.txt
文件非常简单:
cmake_minimum_required(VERSION 3.28) # 故意设置一个很高的版本project(HighTechApp)
add_executable(HighTechApp main.cpp)
如果你在一台只安装了 CMake 3.22 的机器上运行 cmake .
,你将会看到类似这样的错误信息:
CMake Error at CMakeLists.txt:1 (cmake_minimum_required):CMake 3.28 or higher is required. You are running version 3.22.1-- Configuring incomplete, errors occurred!
这个过程会立即失败,不会生成任何构建文件(如 Makefile)。这清楚地告知开发者需要升级他们的 CMake,而不是在后续构建中遇到更晦涩难懂的错误。
我们可以演示一下
我们回去修改一下CMakeLists.txt
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.30)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)
例子 3:使用可选策略版本 (Policy Max)
这个特性用于更精细地控制 CMake 的行为兼容性。
# 要求 CMake 至少是 3.16 版本,但将策略行为设置为与 3.20 版本一致
cmake_minimum_required(VERSION 3.16...3.20)project(MyApp)
# ...
-
作用:
-
和例子1一样,它会阻止 CMake 3.16 以下的版本运行。
-
对于版本在 3.16 到 3.20 之间的 CMake,它会启用所有在 3.20 版本中默认为“新”行为的策略。这确保了即使你用 CMake 3.18 来构建,其行为也会和 3.20 保持一致,从而消除不同小版本之间的行为差异。
-
对于 3.20 及以上版本的 CMake,此命令没有额外效果,直接使用其默认行为即可。
-
-
通俗理解:“我需要至少 3.16 才能工作,但我希望所有 >=3.16 且 <3.20 的版本都模仿 3.20 的行为方式来工作,以保证一致性。”
我们来演示一下
我们回去修改一下我们的CMakeLists.txt
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18...4.0)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)# printf
message(STATUS "PROJECT_NAME: ${PROJECT_NAME}")# print version
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})
1.2.project函数
指定项⽬名字,放在顶级CMakeLists⽂件的第⼆⾏,⼦⽬录中⼀般⽆需调⽤。
# 简短形式:仅指定项目名称和可选语言
project(<PROJECT-NAME> [<language-name>...])
这是最基础、最常用的形式。
-
<PROJECT-NAME>
(必选):-
你的项目名称。这是必须指定的。
-
CMake 会基于这个名称自动创建几个重要的变量,例如:
-
PROJECT_NAME
: 存储就是这个项目名称。 -
PROJECT_SOURCE_DIR
或<PROJECT-NAME>_SOURCE_DIR
: 项目源码的根目录路径(即顶级CMakeLists.txt
所在的目录)。 -
PROJECT_BINARY_DIR
或<PROJECT-NAME>_BINARY_DIR
: 项目构建目录的路径(即运行cmake
命令的目录)。
-
-
示例:
project(MyAwesomeApp)
会定义变量MyAwesomeApp_SOURCE_DIR
。
-
-
[<language-name>...]
(可选):-
指定项目中使用的一种或多种编程语言。例如
C
,CXX
(C++),ASM
(汇编),Fortran
,CUDA
等。 -
如果省略,CMake 默认会启用
C
和CXX
(C 和 C++)。 -
如果提供,CMake 只会启用你明确指定的语言,并会检查系统是否有对应的编译器。如果找不到编译器,配置会失败。
-
示例:
-
project(MyLib C)
: 这是一个只使用 C 语言的项目。 -
project(MyGame CXX)
: 这是一个只使用 C++ 语言的项目。 -
project(MyMixedProject C CXX ASM)
: 这是一个混合使用了 C、C++ 和汇编语言的项目。
-
-
简短形式的使用场景:适用于大多数不需要复杂元信息(如版本号、描述)的小型或个人项目。
# 完整形式:包含所有可选参数
project(<PROJECT-NAME>[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]][DESCRIPTION <project-description-string>][HOMEPAGE_URL <url-string>][LANGUAGES <language-name>...])
完整形式包含了简短形式的所有功能,并增加了几个可选的关键字参数来提供项目的元数据。
-
VERSION
(可选):-
用于指定项目的版本号。版本号可以包含最多四个数字段(主版本、次版本、补丁版本、微调版本)。
-
设置后,CMake 会自动创建一系列相关的版本变量,这些变量在打包或配置头文件时非常有用:
-
PROJECT_VERSION
或<PROJECT-NAME>_VERSION
: 完整的版本字符串(如1.2.3.4
)。 -
PROJECT_VERSION_MAJOR
或<PROJECT-NAME>_VERSION_MAJOR
: 主版本号(如1
)。 -
PROJECT_VERSION_MINOR
或<PROJECT-NAME>_VERSION_MINOR
: 次版本号(如2
)。 -
..._PATCH
,..._TWEAK
同理。
-
-
示例:
VERSION 2.5.1
会设置PROJECT_VERSION
为"2.5.1"
。
-
-
DESCRIPTION
(可选):-
一段简短的文字,描述你的项目是做什么的。
-
会设置变量
PROJECT_DESCRIPTION
。 -
示例:
DESCRIPTION “A cross-platform calculator application”
-
-
HOMEPAGE_URL
(可选):-
项目的主页 URL,例如 GitHub 仓库地址或项目网站。
-
会设置变量
PROJECT_HOMEPAGE_URL
。 -
示例:
HOMEPAGE_URL “https://github.com/me/myproject”
-
-
LANGUAGES
(可选):-
这与简短形式中的
[<language-name>...]
参数功能完全一样,只是使用了更明确的关键字LANGUAGES
。推荐在完整形式中使用它,以提高可读性。 -
示例:
LANGUAGES CXX
-
完整形式的使用场景:适用于更正式、需要分发或集成的项目(特别是库文件)。版本信息对于包管理(如通过 find_package()
查找)和生成配置文件(如 version.h.in
)至关重要。
1.2.1.project函数的使用注意事项
我们去官网看看:project — CMake 4.1.0 Documentation
有下面这么一段话
翻译过来就是下面这样子:
项目中的顶层 CMakeLists.txt 文件必须包含一条直接且字面意义上的 project() 命令调用;通过 include() 命令引入的调用不符合要求。若不存在此类调用,CMake 将发出警告并在顶层隐式添加 project(Project) 命令以启用默认语言(C 和 CXX)。
注意:应在顶层 CMakeLists.txt 文件靠前位置(但需在调用 cmake_minimum_required() 之后)调用 project() 命令。必须先确定版本和政策设置,再调用可能受其影响的其他命令——若未遵守此顺序,project() 命令将发出警告。
我们可以看看把projiect函数删除是什么情况。
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)# 查看当前项目名称
message("PROJECT_NAME: ${PROJECT_NAME}")
# 查看项目启用了哪些语言
message(STATUS "Project Languages: ${PROJECT_LANGUAGES}")
警告信息分析
-
第一个警告:
No project() command is present.
CMake Warning (dev) in CMakeLists.txt:No project() command is present. The top-level CMakeLists.txt file mustcontain a literal, direct call to the project() command.
意思:在你的顶层
CMakeLists.txt
文件中,没有找到project()
命令。这是必须的。 -
CMake 的临时解决方案:
CMake is pretending there is a "project(Project)" command on the first line.
意思:为了能够让配置过程继续下去,CMake 自动模拟了一个
project(Project)
命令。这就是为什么后面还能看到PROJECT_NAME: Project
的原因。这个名字 "Project" 是 CMake 随机选的默认值。 -
第二个警告:
cmake_minimum_required() should be called prior to this top-level project()
CMake Warning (dev) in CMakeLists.txt:cmake_minimum_required() should be called prior to this top-level project() call.
意思:因为 CMake 自动模拟了
project()
命令,但它发现这个模拟的命令出现在cmake_minimum_required()
之前,这不符合规范。正确的顺序应该是先声明所需 CMake 的最低版本,再定义项目。 -
结果输出:
PROJECT_NAME: Project -- Project Languages:
意思:这正是 CMake 自动模拟
project(Project)
命令的证明。它创建了项目名称为 "Project",并且因为没有指定语言(LANGUAGES
),所以PROJECT_LANGUAGES
为空。根据我们之前的讨论,如果不指定语言,CMake 默认会启用C
和CXX
,但在这里的警告模式下,它可能没有完全模拟所有行为。
解决方案:你需要编辑你的 CMakeLists.txt
文件,确保它的开头必须得是下面这两个函数开头,且顺序必须和下面一样。
# 第一步:指定需要的CMake最低版本(必须放在最前面)
cmake_minimum_required(VERSION 3.10) # 版本号根据你的需要指定# 第二步:定义你的项目(这是紧接着必须做的)
project(YourProjectName) # 将 YourProjectName 替换成你真正的项目名# ... 接下来才是其他的命令,比如 add_executable, target_link_libraries 等 ...
1.2.2.project生成的变量
事实上,当我们执行 project()
命令时,CMake 会解析我们提供的参数,并基于这些参数自动创建一系列对应的内部变量。
通过 project()
命令设置的每一个参数都会转化为一个或多个可直接引用的 CMake 变量。
你写的命令:
project(MyAwesomeAppVERSION 1.2.3DESCRIPTION "A fantastic application"LANGUAGES CXX
)
CMake 收到后,立刻在后台为你创建了以下变量:
-
PROJECT_NAME = "MyAwesomeApp"
-
因为你提供了项目名
MyAwesomeApp
。
-
-
PROJECT_VERSION = "1.2.3"
PROJECT_VERSION_MAJOR = 1
PROJECT_VERSION_MINOR = 2
PROJECT_VERSION_PATCH = 3
-
因为你提供了版本号
VERSION 1.2.3
,CMake 还贴心地把完整版本拆成了几个部分。
-
-
PROJECT_DESCRIPTION = "A fantastic application"
-
因为你提供了描述
DESCRIPTION "A fantastic application"
。
-
-
CMake 还自动设置了编译器
-
因为你通过
LANGUAGES CXX
指定了语言,CMake 会立刻去系统里查找 C++ 编译器,并把找到的编译器路径和信息也存成变量(比如CMAKE_CXX_COMPILER
)。
-
接下来,你就可以像使用普通变量一样使用它们!
这些自动生成的变量,在你的 CMake 脚本里随处可用。比如:
-
用名字来创建目标,保证名称统一:
add_executable(${PROJECT_NAME} main.cpp) # 等价于 add_executable(MyAwesomeApp main.cpp)
-
用版本号来设置库的属性,这是标准做法:
set_target_properties(${PROJECT_NAME} PROPERTIESVERSION ${PROJECT_VERSION} # 设置完整版本SOVERSION ${PROJECT_VERSION_MAJOR} # 设置主版本,用于兼容性控制 )
-
用版本信息来生成代码里的头文件,杜绝手动错误:
# 把一个模板文件 version.h.in 里的占位符 @PROJECT_VERSION@,替换成真实的 "1.2.3" configure_file(version.h.in version.h)
-
打印信息,让你知道正在配置哪个项目:
message("正在构建项目: ${PROJECT_NAME}, 版本: ${PROJECT_VERSION}")
核心思想一句话总结:
project()
命令就是你告诉 CMake 项目核心信息的地方,说完之后,你就可以用 ${变量名}
的方式,在后续任何地方重复使用这些信息。 这保证了信息的唯一性和正确性,是写好 CMake 脚本的关键第一步。
project() 执⾏之后,CMake会根据参数来⾃动创建以下变量(我只列出了比较重要的一些),可在后续命令中使⽤:
变量名 | 描述 | 示例值 |
---|---|---|
项目信息变量 | ||
PROJECT_NAME | 当前项目的名称,由 project() 命令设置。 | MyProject |
CMAKE_PROJECT_NAME | 整个构建树中第一个通过 project() 命令定义的项目的名称。 | MyProject |
PROJECT_LANGUAGES | 项目中启用的编程语言列表(如 C、CXX)。 | C CXX |
版本信息变量 | ||
PROJECT_VERSION | 项目的完整版本字符串。 | 1.2.3 |
PROJECT_VERSION_MAJOR | 项目的主版本号。 | 1 |
PROJECT_VERSION_MINOR | 项目的次版本号。 | 2 |
PROJECT_VERSION_PATCH | 项目的修订号(补丁版本)。 | 3 |
目录路径变量 | ||
CMAKE_SOURCE_DIR | 顶级项目的源代码目录的绝对路径。 | /home/user/project |
CMAKE_BINARY_DIR | 顶级项目的构建目录的绝对路径。 | /home/user/project/build |
PROJECT_SOURCE_DIR | 当前项目的源代码目录的绝对路径。 | /home/user/project |
PROJECT_BINARY_DIR | 当前项目的构建目录的绝对路径。 | /home/user/project/build |
CMAKE_CURRENT_SOURCE_DIR | 当前正在处理的 CMakeLists.txt 文件所在的源代码目录。 | /home/user/project/src |
CMAKE_CURRENT_BINARY_DIR | 与 CMAKE_CURRENT_SOURCE_DIR 对应的构建目录。 | /home/user/project/build/src |
文件路径变量 | ||
CMAKE_CURRENT_LIST_FILE | 当前正在处理的 CMakeLists.txt 文件的完整路径。 | /home/user/project/src/CMakeLists.txt |
CMAKE_CURRENT_LIST_DIR | 当前正在处理的 CMakeLists.txt 文件所在的目录。 | /home/user/project/src |
安装路径变量 | ||
CMAKE_INSTALL_PREFIX | 项目安装的根目录。默认值因平台而异(Unix: /usr/local , Windows: C:/Program Files )。 | /usr/local |
CMAKE_INSTALL_BINDIR | 相对于安装前缀的可执行文件安装目录。通常遵循 GNU 编码标准,默认为 bin 。 | bin |
我们可以修改一下我们的CMakeLists.txt来看看
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18...4.0)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)# 输出⼀些调试信息# namemessage(STATUS "PROJECT_NAME: ${PROJECT_NAME}")message(STATUS "CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}")message(STATUS "PROJECT_LANGUAGES: ${PROJECT_LANGUAGES}")# versionmessage(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}")# dirmessage(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")message(STATUS "PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")message(STATUS "CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")
# filemessage(STATUS "CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}")message(STATUS "CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")
# 输出当前的 CMAKE_INSTALL_PREFIX
message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}")
# 输出当前的 CMAKE_INSTALL_BINDIR
message(STATUS "CMAKE_INSTALL_BINDIR: ${CMAKE_INSTALL_BINDIR}")
我们说project创建的变量可以在后续的命令中使用,我们看看几个例子就明白了
示例 :使用 PROJECT_NAME
定义目标
cmake_minimum_required(VERSION 3.10)
project(MyApplication) # 这里设置了 PROJECT_NAME# 使用变量而不是硬编码名称
add_executable(${PROJECT_NAME} main.cpp)
详细解释:
-
project(MyApplication)
执行后,CMake 自动创建变量PROJECT_NAME
,其值为"MyApplication"
。 -
${PROJECT_NAME}
在add_executable()
命令中被展开(替换)为它的值"MyApplication"
。 -
最终效果 等同于直接写
add_executable(MyApplication main.cpp)
。
为什么这样用好?
-
避免硬编码:如果你的项目需要改名,只需要修改
project()
这一行,所有使用${PROJECT_NAME}
的地方都会自动更新。 -
提高可维护性:确保了整个项目中目标名称的一致性。
1.2.3.project_name参数相关的变量
注意:这个小节里面只列举了最常用的变量
当你执行这条最简单的命令时:
project(MyApplication)
CMake 会立刻为你创建两组至关重要的变量:项目标识变量和项目路径变量。同时,它还会触发一个重要的默认行为。
1. 项目标识变量 (Project Identity Variables)
这些变量直接存储了你提供的项目名称,用于在脚本中引用你的项目。
-
PROJECT_NAME
-
含义:当前在
project()
命令中定义的项目名称。 -
值:
MyApplication
-
特点:它的值取决于当前所在的作用域。如果你在子目录的
CMakeLists.txt
中也调用了project()
,那么在这个子目录中,PROJECT_NAME
的值会变成子项目的名称。 -
用途:这是最常用的变量,用于定义目标、输出消息等,确保与项目名保持一致。
add_executable(${PROJECT_NAME} main.cpp) # 创建名为 "MyApplication" 的可执行文件 message("Building project: ${PROJECT_NAME}")
-
-
CMAKE_PROJECT_NAME
-
含义:整个构建过程中,第一个被调用的
project()
命令中指定的项目名称。也称为顶级项目名。 -
值:
MyApplication
(假设这是在顶级CMakeLists.txt
中调用的) -
特点:这是一个全局常量。在整个构建过程中,无论你在哪个子目录、哪个子项目中访问它,它的值永远不会改变。
-
用途:当你需要始终引用顶级项目名称时使用。例如,在一个包含多个子项目的大型项目中,你想在子项目的脚本中获取顶级项目的名称。
# 在子项目的CMakeLists.txt中 message("This is a sub-project of: ${CMAKE_PROJECT_NAME}")
-
简单比喻:
-
PROJECT_NAME
像是你当前所在房间的号码。 -
CMAKE_PROJECT_NAME
像是整栋大楼的名字。 -
如果你就在大楼的主厅里(顶级目录),那么这两个名字指的是同一个地方。但如果你走到了侧翼的一个会议室(子目录),
PROJECT_NAME
就变成了会议室的号码,而CMAKE_PROJECT_NAME
仍然是大楼的名字。
我们看看
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3.4LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)# 输出⼀些调试信息
message("PROJECT_NAME: ${PROJECT_NAME}")
message("CMAKE_PROJECT_NAME: ${CMAKE_PROJECT_NAME}")
嗯!很好
1.2.4.version参数相关的变量
注意:这个小节里面只列举了最常用的变量
project(<PROJECT-NAME>[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]][COMPAT_VERSION <major>[.<minor>[.<patch>[.<tweak>]]]][DESCRIPTION <project-description-string>][HOMEPAGE_URL <url-string>][LANGUAGES <language-name>...])
我们来深入、详细地讲解一下执行带 VERSION
参数的 project()
命令后,CMake 会生成哪些与版本相关的变量,以及它们的具体含义和用途。
当你执行这样一条命令时:
project(MyProject VERSION 1.2.3.4)
CMake 不仅会定义项目名称,还会因为你提供了 VERSION
参数,自动创建两套与版本相关的变量。
第一套变量:以
PROJECT_
为前缀
这套变量是最常用的,它们直接指向当前项目的版本信息。无论你的 CMakeLists.txt 是在顶级目录还是子目录中,在这些文件里使用 PROJECT_
前缀的变量,获取到的都是当前所在 CMakeLists.txt 对应的 project()
命令所定义的版本。
-
PROJECT_VERSION
-
含义:完整的版本号字符串。
-
值:
"1.2.3.4"
-
用途:当你需要完整的版本字符串时使用,例如生成配置文件、打包发布时命名压缩包。
-
-
PROJECT_VERSION_MAJOR
-
含义:主版本号(Major)。通常意味着做了不兼容的 API 修改。
-
值:
1
-
用途:通常用作共享库的
SOVERSION
,表示接口兼容性。
-
-
PROJECT_VERSION_MINOR
-
含义:次版本号(Minor)。通常意味着做了向下兼容的功能性新增。
-
值:
2
-
用途:用于详细的版本信息展示和判断。
-
-
PROJECT_VERSION_PATCH
-
含义:修订号(Patch)。通常意味着做了向下兼容的问题修正。
-
值:
3
-
用途:用于更细粒度的版本信息展示和判断。
-
-
PROJECT_VERSION_TWEAK
-
含义:微调版本号(Tweak)。用于更微小的变更。
-
值:
4
-
注意:这是一个可选的版本段。如果你的版本号是
1.2.3
,那么这个变量将不会被定义。
-
我们来看看,我们修改一下CMakeLists
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3.4LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)# 输出⼀些调试信息# namemessage(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 "PROJECT_VERSION_TWEAK: ${PROJECT_VERSION_TWEAK}")
第二套变量:以项目名为前缀 (
<PROJECT-NAME>_
)
这套变量是第一套变量的别名(Alias)。它们的功能与第一套变量完全一致,但变量名中包含了项目名,使得指向更加明确。
-
MyProject_VERSION
-
值:
"1.2.3.4"
(与PROJECT_VERSION
相同)
-
-
MyProject_VERSION_MAJOR
-
值:
1
(与PROJECT_VERSION_MAJOR
相同)
-
-
MyProject_VERSION_MINOR
-
值:
2
(与PROJECT_VERSION_MINOR
相同)
-
-
MyProject_VERSION_PATCH
-
值:
3
(与PROJECT_VERSION_PATCH
相同)
-
-
MyProject_VERSION_TWEAK
-
值:
4
(与PROJECT_VERSION_TWEAK
相同)
-
我们也去把CMakeLists修改一下
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3.4LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)# 输出⼀些调试信息
message(STATUS "${PROJECT_NAME}_VERSION: ${${PROJECT_NAME}_VERSION}")
message(STATUS "${PROJECT_NAME}_VERSION_MAJOR: ${${PROJECT_NAME}_VERSION_MAJOR}")
message(STATUS "${PROJECT_NAME}_VERSION_MINOR: ${${PROJECT_NAME}_VERSION_MINOR}")
message(STATUS "${PROJECT_NAME}_VERSION_PATCH: ${${PROJECT_NAME}_VERSION_PATCH}")
message(STATUS "${PROJECT_NAME}_VERSION_TWEAK: ${${PROJECT_NAME}_VERSION_TWEAK}")
为什么需要两套变量?
主要是为了提高脚本的清晰度和可维护性。
在复杂的项目中,如果你使用了 add_subdirectory()
包含了多个子项目,每个子项目都有自己的 project()
命令。
在子项目的 CMakeLists.txt 中,PROJECT_VERSION
指向的是当前子项目的版本。
而如果你在父项目的脚本中想引用某个特定子项目(例如 MyProject
)的版本,使用 MyProject_VERSION
就会非常清晰,不会产生歧义。
1.2.5.language参数相关的变量
注意:这个小节里面只列举了最常用的变量
1. 编译器识别变量
CMAKE_CXX_COMPILER
(对应 C 语言是 CMAKE_C_COMPILER
)
-
用途:C++ 编译器的完整路径
-
为什么重要:这是构建的基础,CMake 靠它来编译代码
-
示例值:
/usr/bin/g++
或/usr/bin/clang++
CMAKE_CXX_COMPILER_ID
-
用途:编译器厂商标识
-
为什么重要:用于编写跨编译器平台的条件代码
-
常用值:
GNU
(GCC)、Clang
、MSVC
、AppleClang
CMAKE_CXX_COMPILER_VERSION
-
用途:编译器版本号
-
为什么重要:用于检查编译器是否满足最低版本要求
2. 编译标志变量
CMAKE_CXX_FLAGS
-
用途:全局的 C++ 编译标志
-
为什么重要:影响所有目标的编译选项
-
注意:现代 CMake 推荐使用
target_compile_options()
代替直接修改此变量
CMAKE_CXX_FLAGS_DEBUG
和 CMAKE_CXX_FLAGS_RELEASE
-
用途:针对特定构建类型(Debug/Release)的编译标志
-
为什么重要:自动为不同构建类型设置合适的优化级别和调试信息
-
典型值:
-
Debug:
-g
(生成调试信息) -
Release:
-O3 -DNDEBUG
(最大优化,禁用断言)
-
3. 语言标准变量(最重要的一组)
CMAKE_CXX_STANDARD
-
用途:设置 C++ 语言标准版本
-
为什么重要:决定代码遵循哪个 C++ 标准(C++11/14/17/20)
-
常用设置:
set(CMAKE_CXX_STANDARD 11)
或set(CMAKE_CXX_STANDARD 17)
CMAKE_CXX_STANDARD_REQUIRED
-
用途:是否强制要求编译器支持指定的标准
-
为什么重要:设置为
ON
可确保代码在不同编译器上的一致性
CMAKE_CXX_EXTENSIONS
-
用途:是否启用编译器扩展
-
为什么重要:设置为
OFF
可提高代码的可移植性
我们看看
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)# 打印 C 语言相关变量
message("")
message("=== C 语言配置 ===")
message("C 编译器: ${CMAKE_C_COMPILER}")
message("C 编译器标识: ${CMAKE_C_COMPILER_ID}")
message("C 编译器版本: ${CMAKE_C_COMPILER_VERSION}")
message("C 编译标志: ${CMAKE_C_FLAGS}")
message("C Debug 编译标志: ${CMAKE_C_FLAGS_DEBUG}")
message("C Release 编译标志: ${CMAKE_C_FLAGS_RELEASE}")# 打印 C++ 语言相关变量
message("")
message("=== C++ 语言配置 ===")
message("C++ 编译器: ${CMAKE_CXX_COMPILER}")
message("C++ 编译器标识: ${CMAKE_CXX_COMPILER_ID}")
message("C++ 编译器版本: ${CMAKE_CXX_COMPILER_VERSION}")
message("C++ 编译标志: ${CMAKE_CXX_FLAGS}")
message("C++ Debug 编译标志: ${CMAKE_CXX_FLAGS_DEBUG}")
message("C++ Release 编译标志: ${CMAKE_CXX_FLAGS_RELEASE}")
message("C++ 标准: ${CMAKE_CXX_STANDARD}")
message("C++ 标准必需: ${CMAKE_CXX_STANDARD_REQUIRED}")
message("C++ 扩展: ${CMAKE_CXX_EXTENSIONS}")
我们先不深究到底有哪些变量,这些变量有什么含义,这我们要调用的时候再去官网查询即可。我们不用花太多心思在这上面。
如果我们设置了错误的language怎么办?
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES C
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)
我们看看什么错
-
"Cannot determine link language for target 'hello'"
-
这个错误表示 CMake 无法确定应该使用哪种语言来链接(编译和连接)您的目标
hello
-
链接语言通常由源文件的扩展名决定(如
.c
对应 C,.cpp
对应 C++)
-
-
"CMake can not determine linker language for target: hello"
-
这是前一条错误的更详细表述,确认了 CMake 无法确定链接语言
-
我们再设置成正确的language
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)
很好!!
当然,我们也可以同时设置多种语言的(比如说C和C++的)
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)
1.3.include函数
我们可以去官网看看:include — CMake 4.1.0 Documentation
我们翻译过来就是下面这样子(注意我删掉了不太重要的)
include
命令
功能: 从文件或模块中加载并运行 CMake 代码。
语法:
include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>][NO_POLICY_SCOPE])
说明:
从给定的文件中加载并运行 CMake 代码。对变量的读取和写入操作会访问调用者所在的作用域(动态作用域)。
选项:
-
OPTIONAL
-
如果提供了此选项,那么当指定的文件不存在时,不会报错。
-
-
RESULT_VARIABLE <var>
-
如果提供了此选项,变量
<var>
将被设置为已包含文件的完整路径名。 -
如果包含操作失败(且未使用
OPTIONAL
),则该变量<var>
会被设置为NOTFOUND
。
-
文件与模块的搜索路径:
-
如果指定的是一个文件(
<file>
),则直接包含该文件。 -
如果指定的是一个模块(
<module>
),CMake 会按以下顺序搜索名为<modulename>.cmake
的文件:-
首先在
CMAKE_MODULE_PATH
变量指定的路径中查找。 -
然后在 CMake 的内置模块目录(cmake module directory)中查找。
-
-
一个例外情况:如果调用
include
命令的.cmake
文件本身位于 CMake 的内置模块目录中,那么搜索顺序会变为:-
首先在 CMake 的内置模块目录中查找。
-
然后在
CMAKE_MODULE_PATH
指定的路径中查找。
-
1.3.1.示例
例子 1:包含必需文件
include(MyHelpers.cmake)
详细解释:
-
作用:直接包含并执行
MyHelpers.cmake
文件中的代码 -
查找方式:在当前目录或 CMake 模块路径中查找
MyHelpers.cmake
文件 -
错误处理:如果找不到该文件,CMake 会立即报错并停止执行
-
适用场景:当你有一个包含重要函数、宏或配置的文件,且这个文件必须存在时使用
实际示例:
假设 MyHelpers.cmake
内容:
# 定义一些实用函数
function(setup_target TARGET_NAME)target_compile_features(${TARGET_NAME} PRIVATE cxx_std_17)target_compile_options(${TARGET_NAME} PRIVATE -Wall -Wextra)
endfunction()
在 CMakeLists.txt
中使用:
include(MyHelpers.cmake) # 必须要有这个文件add_executable(my_app main.cpp)
setup_target(my_app) # 使用包含文件中定义的函数
例子 2:包含可选文件
include(OptionalSettings.cmake OPTIONAL)
详细解释:
-
作用:尝试包含
OptionalSettings.cmake
文件,但如果文件不存在也不会报错 -
查找方式:与第一个例子相同
-
错误处理:使用
OPTIONAL
关键字,文件不存在时不会中断 CMake 执行 -
适用场景:用于那些可有可无的配置文件,比如用户自定义的覆盖设置
实际示例:
假设你有默认配置,但允许用户提供自定义配置:
# 先设置默认值
set(BUILD_TYPE "Release")
set(ENABLE_DEBUG OFF)# 然后尝试包含用户自定义配置(如果有的话)
include(OptionalSettings.cmake OPTIONAL)# 无论用户配置文件是否存在,都会继续执行
message("构建类型: ${BUILD_TYPE}")
用户可以在 OptionalSettings.cmake
中覆盖默认值:
# 用户自定义设置
set(BUILD_TYPE "Debug")
set(ENABLE_DEBUG ON)
这样子如果 OptionalSettings.cmake存在则BUILD_TYPE就是Debug,否则就是默认的Release。
例子 3:包含模块
include(FindGTest)
详细解释:
-
作用:让 CMake 自动查找名为
FindGTest.cmake
的模块文件 -
查找方式:
-
首先在
CMAKE_MODULE_PATH
变量指定的目录中查找 -
然后在 CMake 的内置模块目录中查找
-
-
模块特性:不需要写文件扩展名
.cmake
,CMake 会自动添加 -
适用场景:使用 CMake 提供的或第三方库的查找模块
实际示例:
# 设置额外的模块搜索路径(可选)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")# 包含查找模块
include(FindGTest)# 现在可以使用模块定义的变量
if(GTest_FOUND)message(STATUS "找到 GTest: ${GTEST_LIBRARIES}")include_directories(${GTEST_INCLUDE_DIRS})
else()message(WARNING "未找到 GTest,测试功能将不可用")
endif()
例子 4:包含文件并获取结果
include(CMakeColors.cmake RESULT_VARIABLE included_file_path)
message(STATUS "被包含的文件是: ${included_file_path}")
详细解释:
-
作用:包含文件并将包含结果保存到指定变量中
-
结果值:
-
如果包含成功:变量值为被包含文件的完整路径
-
如果包含失败:变量值为
NOTFOUND
-
-
适用场景:需要知道包含操作是否成功,或者需要获取文件的确切路径时
实际示例:
# 尝试包含文件并捕获结果
include(MyConfig.cmake RESULT_VARIABLE config_file)# 根据结果采取不同行动
if(config_file STREQUAL "NOTFOUND")message(WARNING "配置文件未找到,使用默认配置")set(USE_CUSTOM_CONFIG OFF)
else()message(STATUS "使用自定义配置文件: ${config_file}")set(USE_CUSTOM_CONFIG ON)
endif()# 或者更简洁的判断
if(NOT config_file STREQUAL "NOTFOUND")message(STATUS "成功加载配置: ${config_file}")
endif()
1.3.2.CMake进⼊⼦⽬录之后,内置路径变量的变化情况
我们这里专门讲解CMAKE_CURRENT_SOURCE_DIR和
当然,我们还需要创建一个build目录
CMakeLists.txt
# 1 设置版本要求
# 指定CMake的最低版本要求为3.18,确保使用兼容的功能
cmake_minimum_required(VERSION 3.18)# 2 设置项目名称
# 定义项目名称为"TestInclude",这会初始化一些基本变量
project(TestInclude)# 3 打印内置路径变量
# 输出一条状态消息,标识当前执行的是顶级CMakeLists.txt文件
message(STATUS "from top-level CMakeLists.txt")# 打印当前正在处理的源代码目录 - 即当前CMakeLists.txt文件所在的目录
# 这个变量在包含子目录或子项目时会变化
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")# 打印当前正在处理的CMake脚本文件的完整路径和文件名
# 这对于调试和了解执行流程很有帮助
message(STATUS "CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}")# 打印当前正在处理的CMake脚本文件所在的目录路径
# 这是CMAKE_CURRENT_LIST_FILE的目录部分,常用于构建相对路径
message(STATUS "CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")# 4 包含子目录cmake脚本
# 使用include命令加载并执行sub目录下的sub.cmake文件
# include命令会将指定文件的内容插入到当前位置执行
# 被包含的文件可以访问和修改当前作用域中的变量
include(sub/sub.cmake)
sub.cmake
# 输出状态消息,表明当前执行的是 sub/sub.cmake 文件
message(STATUS "from sub/sub.cmake")# 打印当前正在处理的源代码目录
# 注意:在包含的文件中,这个值可能与顶级CMakeLists.txt中的不同
# 它表示的是包含此文件的CMakeLists.txt所在的目录,而不是当前文件所在目录
message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")# 打印当前正在执行的CMake脚本的完整路径和文件名
# 这个变量会显示当前实际执行的文件路径,在这里应该是sub/sub.cmake的完整路径
message(STATUS "CMAKE_CURRENT_LIST_FILE: ${CMAKE_CURRENT_LIST_FILE}")# 打印当前正在执行的CMake脚本文件所在的完整目录路径
# 这个变量是CMAKE_CURRENT_LIST_FILE的目录部分
# 在这里应该是sub/sub.cmake文件所在的目录路径
message(STATUS "CMAKE_CURRENT_LIST_DIR: ${CMAKE_CURRENT_LIST_DIR}")
我们仔细观察这个变化
在 CMake 中,当你使用 include()
时,理解两个“当前”路径至关重要:
-
项目的老家 (
CMAKE_CURRENT_SOURCE_DIR
)-
它指的是什么:最开始那个
CMakeLists.txt
文件所在的目录。你可以把它想象成项目的“根目录”或“老家”。 -
它变不变:基本不变。不管你在项目里怎么
include
其他文件,这个“老家”的地址是不会变的。它始终记录着你是从哪里启动cmake
命令的。
-
-
你当前在哪儿 (
CMAKE_CURRENT_LIST_DIR
)-
它指的是什么:正在被执行的那个 CMake 脚本文件所在的目录。
-
它变不变:会变。当 CMake 执行到
include(sub/sub.cmake)
时,它就会跳转到sub.cmake
文件内部去执行其中的命令。这时,“你当前在哪儿”就变成了sub
目录。
-
用我的例子来拆解
我的项目结构:
/root/cmake/test_include/ (<-- 这就是“老家”)
├── CMakeLists.txt
├── build/
└── sub/└── sub.cmake
第一段输出(执行到主列表时):
-- from top-level CMakeLists.txt
-- CMAKE_CURRENT_SOURCE_DIR:/root/cmake/test_include # <-- 老家地址
-- CMAKE_CURRENT_LIST_FILE:/root/cmake/test_include/CMakeLists.txt # <-- 正在看老家地图
-- CMAKE_CURRENT_LIST_DIR:/root/cmake/test_include # <-- 正站在老家里
-
此时:CMake 正在执行主目录下的
CMakeLists.txt
文件。 -
所以,“老家”和“当前位置”都是同一个地方:
/root/cmake/test_include
。
第二段输出(执行到子文件时):
-- from sub/sub.cmake
-- CMAKE_CURRENT_SOURCE_DIR:/root/cmake/test_include # <-- 老家地址还是没变!
-- CMAKE_CURRENT_LIST_FILE:/root/cmake/test_include/sub/sub.cmake # <-- 正在看子地图
-- CMAKE_CURRENT_LIST_DIR:/root/cmake/test_include/sub # <-- 现在站在子目录里
-
此时:CMake 通过
include(sub/sub.cmake)
这条命令,跳转去执行sub.cmake
文件里的内容了。 -
关键变化:
-
CMAKE_CURRENT_SOURCE_DIR
(老家):没变!还是最初的项目根目录。这很重要,因为它提供了一个固定的参考点。 -
CMAKE_CURRENT_LIST_FILE
和CMAKE_CURRENT_LIST_DIR
(当前位置):变了!它们现在指向的是正在被执行的sub.cmake
文件和它所在的sub
目录。
-
一句话总结
-
想找项目根目录(放公共头文件、库的地方)?用
CMAKE_CURRENT_SOURCE_DIR
。 -
想找当前脚本文件自己的目录(用它旁边的其他文件)?用
CMAKE_CURRENT_LIST_DIR
。
对⽐发现,只有CMAKE_CURRENT_LIST_FILE和CMAKE_CURRENT_LIST_DIR能真实定位正在执⾏ 的cmake⽂件,不论是include还是add_subdirectory命令。
总结
在CMake构建过程中,CMAKE_CURRENT_LIST_FILE和CMAKE_CURRENT_LIST_DIR是两个至关重要的路径变量,它们能够唯一且准确地定位当前正在执行的CMake脚本文件的具体位置。与其他路径变量相比,这两个变量具有独特的实时性和精确性,无论是在include()命令包含文件时,还是在add_subdirectory()命令进入子目录时,都能够真实反映当前的执行上下文。
当使用include()命令包含外部CMake脚本时,
- CMAKE_CURRENT_LIST_FILE会立即更新为被包含文件的完整路径
- CMAKE_CURRENT_LIST_DIR则会相应地更新为该文件所在的目录路径
这种特性使得开发者能够在被包含的文件中准确地基于自身路径定位相关资源,而不受主CMakeLists.txt文件位置的影响。
1.4.install函数
我们可以先去官网看看:install — CMake 4.1.0 Documentation
install(TARGETS <targets>... [EXPORT <export-name>][RUNTIME DESTINATION <dir>][LIBRARY DESTINATION <dir>][ARCHIVE DESTINATION <dir>][INCLUDES DESTINATION <dir>][...])install(FILES <files>... DESTINATION <dir>[PERMISSIONS <permissions>...][CONFIGURATIONS <configs>...][COMPONENT <component>][...])install(DIRECTORY <dirs>... DESTINATION <dir>[FILE_PERMISSIONS <permissions>...][DIRECTORY_PERMISSIONS <permissions>...][...])install(EXPORT <export-name> DESTINATION <dir>[NAMESPACE <namespace>::][FILE <filename>][...])
示例
例子 1:安装可执行文件
add_executable(my_app main.cpp)
install(TARGETS my_app DESTINATION bin)
📖 说明:
这里我们首先用 add_executable()
定义了一个可执行程序 my_app
。接着用 install()
告诉 CMake,当执行 cmake --install
的时候,要把 my_app
这个目标安装到 bin
目录。
这样安装完成后,你会在 install/bin/
(假设 --prefix ./install
)目录下看到 my_app
,可以直接运行它。
例子 2:安装库和头文件
add_library(my_lib my_lib.cpp)
install(TARGETS my_lib DESTINATION lib)
install(FILES my_lib.h DESTINATION include)
📖 说明:
这里定义了一个库 my_lib
,用 install(TARGETS ...)
把编译后的库文件安装到 lib/
目录。
同时,我们还用 install(FILES ...)
把 my_lib.h
头文件安装到 include/
目录。
这样别人使用你的库时,只需要包含 include/my_lib.h
,并链接 lib/libmy_lib.a
或 libmy_lib.so
就能使用了。
例子 3:安装配置文件
install(FILES config.json DESTINATION share/my_app)
📖 说明:
有时候程序需要额外的配置文件或数据文件。上面这行代码的意思是,把 config.json
安装到 share/my_app/
目录。
安装完成后,目录结构可能是:
install/└── share/└── my_app/└── config.json
这样程序运行时就能找到配置文件。
例子 4:安装整个目录
install(DIRECTORY assets/ DESTINATION share/my_app/assets)
📖 说明:
当你有一整个目录需要拷贝(比如图片、音频、数据文件等),可以用 install(DIRECTORY ...)
。
这里会把 assets/
目录完整复制到 share/my_app/assets/
下,保持原有的层次结构。
例子 5:完整的目标安装写法
install(TARGETS my_app my_lib RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib )
📖 说明:
这是比较完整的写法。
-
RUNTIME
指可执行文件(Windows 下的.exe
,Linux 下的普通可执行文件) -
LIBRARY
指动态库(Linux.so
,Mac.dylib
,Windows.dll
) -
ARCHIVE
指静态库(Linux.a
,Windows.lib
)
这种写法适合通用性强的项目,因为不管目标是可执行程序还是库,都能被正确安装。
install的执行过程
install()
命令我先不想讲太多,我们先简单看看即可
执行过程分为两个大阶段:
阶段一:配置阶段(生成“说明书”)
当你运行 cmake
配置项目时,所有遇到的 install()
命令都会被 CMake 记录下来。
-
收集指令:CMake 会按顺序(包括在父目录和子目录中遇到的顺序)把所有
install()
命令收集起来。-
比如:先安装可执行文件到
bin
目录,再安装库文件到lib
目录,最后安装头文件到include
目录。
-
-
生成脚本:CMake 最终会在你的构建目录(如
build/
)里生成一个文件(通常是cmake_install.cmake
)。这个文件就是那份详细的《说明书》,里面包含了所有拷贝、创建目录等具体操作的指令。
这个阶段,install()
命令只是被记录,并没有真正执行任何文件操作。
阶段二:安装阶段(工人按“说明书”执行)
当你运行 cmake --install .
或 make install
(Unix) 或 ninja install
时,真正的安装才开始。
-
读取脚本:CMake 会找到在配置阶段生成的那个“说明书”(
cmake_install.cmake
)。 -
按序执行:然后,它会严格按照“说明书”里记录的顺序,一条一条地执行安装命令:
-
创建目标目录(如
/usr/local/bin
)。 -
将指定的文件从构建目录拷贝到安装目录。
-
设置文件的权限(如可执行权限)。
-
我们回去修改一下,看看安装目录在哪里
# 1 设置最低cmake 版本
cmake_minimum_required(VERSION 3.18)#2 设置项目名称
project(InstallHelloVERSION 1.2.3LANGUAGES C CXX
)# 3 添加构建目标
add_executable(hello main.cpp)# 4 安装到本地
include(GNUInstallDirs)
install(TARGETS hello)#打印默认的安装目录
message(STATUS "CMAKE_INSTALL_PREFIX:" ${CMAKE_INSTALL_PREFIX})
1.5.add_executable函数
我们可以去官网看看:add_executable — CMake 4.1.0 Documentation
函数作用
此命令用于指示 CMake 构建系统从一个或多个源文件编译生成一个可执行文件(executable)。它是定义项目最终可运行程序的核心指令。
基本语法形式
add_executable(<target_name>[WIN32][MACOSX_BUNDLE][EXCLUDE_FROM_ALL][source1] [source2 ...]
)
关键参数说明
-
<target_name>
指定要创建的可执行文件的目标名称。该名称在项目范围内必须唯一,且不应包含任何路径信息、空格或特殊字符。CMake 会根据目标平台自动为其添加适当的可执行文件扩展名(例如,在 Windows 下会生成.exe
,而在 Unix-like 系统下则不添加扩展名)。 -
[WIN32]
(可选)
此选项仅适用于 Windows 平台。它指示 CMake 在创建可执行文件时将其链接为 Windows 应用程序(使用WinMain
作为入口点而非标准的main
),并在运行时不会附加控制台窗口。生成的可执行文件通常将带有.exe
后缀。 -
MACOSX_BUNDLE
(可选)
此选项仅适用于 macOS 平台。它指示 CMake 将可执行文件打包为一个 macOS 应用程序包(.app
bundle),这是一种包含可执行文件、资源文件和信息属性列表(Info.plist)的目录结构。 -
EXCLUDE_FROM_ALL
(可选)
若指定此选项,则该可执行文件目标不会被默认构建。这意味着当直接运行make
或cmake --build .
而不指定具体目标时,此可执行文件不会被编译。要构建它,需要显式地指定其目标名(例如make MyApp
)。这对于那些非项目主体功能、仅为示例或测试用途的可执行文件非常有用。 -
[source1] [source2 ...]
这是构建可执行文件所必需的源文件列表。列表可以包含一个或多个源文件(例如.c
,.cpp
,.cc
等)。如果源文件与 CMakeLists.txt 不在同一目录,则需要提供相对或绝对路径。
输出位置与控制
默认情况下,生成的可执行文件将位于与当前 CMakeLists.txt
源目录相对应的构建树(Build Tree)目录中。例如,若在 <source-dir>/app/CMakeLists.txt
中调用此命令,则默认输出路径通常为 `<build-dir>/app/。
若要自定义可执行文件的输出路径,可以通过设置目标的 RUNTIME_OUTPUT_DIRECTORY
属性来实现。例如:
add_executable(MyApp main.cpp)
set_target_properties(MyApp PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
CMake
add_executable
命令示例
1. 普通可执行文件示例
示例 1.1: 基本用法
# 从单个源文件创建可执行文件
add_executable(hello_world main.cpp)
👉 这是最简单的用法,定义了一个名为 hello_world
的可执行文件,它会从 main.cpp
编译而来。编译完成后,你会在构建目录下得到一个 hello_world
程序。
示例 1.2: 多个源文件
# 从多个源文件创建可执行文件
add_executable(my_app main.cpp utils.cpp helper.cpp )
👉 这里定义了一个可执行文件 my_app
,它由三个源文件一起构建。CMake 会自动处理依赖关系,确保所有源文件都参与编译并链接。
示例 1.3: 使用选项
# 创建 Windows 应用程序(无控制台窗口)
add_executable(my_win_app WIN32 main.cpp)# 创建 macOS 应用程序包
add_executable(my_mac_app MACOSX_BUNDLE main.cpp)# 创建但不包含在默认构建中
add_executable(my_tool EXCLUDE_FROM_ALL tool.cpp)
👉 这里展示了几个常见的选项:
-
WIN32
:在 Windows 上生成一个 窗口程序(不会弹出控制台窗口)。 -
MACOSX_BUNDLE
:在 macOS 上生成一个 App Bundle,而不仅仅是一个普通的二进制。 -
EXCLUDE_FROM_ALL
:该目标不会自动编译,必须手动指定才会构建。
示例 1.4: 使用生成器表达式
# 根据配置选择不同的源文件
add_executable(my_appmain.cpp$<$<CONFIG:Debug>:debug_utils.cpp>$<$<CONFIG:Release>:release_utils.cpp>
)
👉 这个例子使用了 生成器表达式。意思是:
-
如果是 Debug 模式,就会额外编译
debug_utils.cpp
。 -
如果是 Release 模式,就会额外编译
release_utils.cpp
。
这样可以根据不同的构建配置选择不同的源文件。
示例 1.5: 稍后添加源文件
# 先创建目标,稍后添加源文件
add_executable(my_app) # 不指定源文件# 稍后添加源文件
target_sources(my_app PRIVATE main.cpp utils.cpp)
👉 在有些情况下,源文件可能比较复杂或需要根据条件决定。
这里先定义了一个 my_app
可执行文件目标,但不指定源文件。然后用 target_sources()
在后面把源文件补充进去。
2. 导入的可执行文件示例
示例 2.1: 基本导入
# 导入系统上的 Python 解释器
add_executable(Python::Interpreter IMPORTED)
set_property(TARGET Python::Interpreter PROPERTY IMPORTED_LOCATION "/usr/bin/python3")
👉 这里创建了一个 导入的可执行文件 Python::Interpreter
,它不会被编译,而是直接指向系统上的 /usr/bin/python3
。这样就可以像使用项目里的目标一样去引用 Python。
示例 2.2: 全局导入
# 全局导入 Git 可执行文件(在所有目录中可用)
add_executable(Git::Git IMPORTED GLOBAL)
set_property(TARGET Git::Git PROPERTY IMPORTED_LOCATION "/usr/bin/git")
👉 这里导入了系统上的 Git 工具,并且加上了 GLOBAL
,意味着在项目的任何子目录中都可以使用 Git::Git
。
示例 2.3: 在不同配置中使用不同的可执行文件
# 导入可执行文件并设置不同配置的路径
add_executable(MyTool IMPORTED)# 设置默认位置
set_property(TARGET MyTool PROPERTY IMPORTED_LOCATION "/usr/bin/mytool")# 设置调试版本的位置
set_property(TARGET MyTool PROPERTY IMPORTED_LOCATION_DEBUG "/usr/bin/mytool_debug")# 在自定义命令中使用导入的可执行文件
add_custom_command(OUTPUT generated_file.cppCOMMAND MyTool::MyTool -i input.txt -o generated_file.cppDEPENDS input.txt
)
👉 这里导入了一个工具 MyTool
,并指定了不同配置下的可执行文件路径:
-
默认使用
/usr/bin/mytool
。 -
Debug 模式下使用
/usr/bin/mytool_debug
。
此外,还展示了在add_custom_command()
中调用导入目标的用法。
3. 别名可执行文件示例
示例 3.1: 创建简单别名
# 创建可执行文件
add_executable(my_application main.cpp)# 为其创建别名
add_executable(app ALIAS my_application)# 现在可以使用 app 或 my_application 引用同一个目标
👉 这里 my_application
是实际目标,app
是它的别名。
在后续代码中,你既可以写 app
,也可以写 my_application
,它们引用的是同一个东西。
示例 3.2: 为导入目标创建别名
# 导入系统工具
add_executable(SystemPython IMPORTED GLOBAL)
set_property(TARGET SystemPython PROPERTY IMPORTED_LOCATION "/usr/bin/python3")# 为导入目标创建别名
add_executable(python ALIAS SystemPython)# 在自定义命令中使用别名
add_custom_command(OUTPUT output.txtCOMMAND python -c "print('Hello')" > output.txtDEPENDS input.txt
)
👉 这里导入了系统 Python,并给它取了一个别名 python
。
在自定义命令里,我们就可以写 COMMAND python ...
,而不用关心具体路径。