cmake安装与基础语法学习2 --之CMakeList语法

cmake安装与基础语法学习2 --之CMakeList语法

CMakeLists.txt 的语法

1) project 配置项目信息

例子:

project(CMakeProj VERSION 1.0 LANGUAGES C CXX)

CMakeProj 是项目的名称,1.0是项目的版本号,LANGUAGES CXX表明项目使用的语言为C++。对于C/C++这个语句可以省略,因为默认语言就是C和C++,注意如果只写CXX是不支持C文件的,需要写明LANGUAGES C CXX,对于Fortran等其它语言不可省略

2)set 设定编译

2.1> 指定编程语言版本

set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)

为了在不同机器上编译更加统一,最好指定语言的版本,比如声明C使用c99标准,C++使用c++11标准;

2.2> 配置编译选项

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe -std=c99")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -std=c++11")

通过命令add_compile_options命令可以为所有编译器配置编译选项(同时对多个编译器生效); 通过设置变量CMAKE_C_FLAGS可以配置c编译器的编译选项; 而设置变量CMAKE_CXX_FLAGS可配置针对c++编译器的编译选项。 比如:

add_compile_options(-Wall -Wextra -pedantic -Werror)

2.3> 配置编译类型

set(CMAKE_BUILD_TYPE Debug)

通过设置变量CMAKE_BUILD_TYPE来配置编译类型,可设置为:Debug、Release等.
当然,更好的方式应该是在执行cmake命令的时候通过参数-D指定:

cmake -B build -DCMAKE_BUILD_TYPE=Debug
2.3.1> 按照编译类型设置编译参数
Debug
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
Release
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
2.3.2> 直接设置总参数
Debug:  set( CXX_FLAGS -g -o0 -D GD_TEST )
release:  set( CXX_FLAGS -o2 -D GD_TEST )

编译宏参数GD_TEST

2.4> 添加include目录

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

示例:

include_directories(src/testDir1/include)

通过命令include_directories来设置头文件的搜索目录。
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,可以通过两种方式来进行控制搜索路径添加的方式:
CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过SET 这个cmake 变量为on,可以将添加的头文件搜索路径放在已有路径的前面。
通过AFTER 或者BEFORE 参数,也可以控制是追加还是置前。

2.5> 设置变量

set(linkname "/path/to/foo.sym")

CMake把变量分成普通变量、缓存变量和环境变量三类:
普通变量的含义是在多次生成构建系统的过程中,CMake并没有记住这个变量,而是每一次构建时重新处理,普通变量还有作用域,并不是全局有效的,例如自定义函数中会有独立的变量作用域
缓存变量会被CMake通过缓存文件CMakeCache.txt记下来,在多次生成构建系统时可以重复地、全局地使用
环境变量就是当前CMake进程中获取的环境变量,我们可以获取并使用,也可以进行临时性的修改(不建议)

2.5.1> 普通变量

我们可以在CMakeLists中使用set()命令定义一个普通变量,赋予它一个值(字符串或字符串数组),例如

set(Var "value") 或set(Var value)
# 通常使用${Var}访问变量,以字符串替换的形式获取变量的值。
message("Var=${Var}")  # Var=value

对于字符串列表,可以用很多种等价的定义形式,其中的分号;被用作字符串的分隔符。

# 对字符串用分号分隔代表列表
set(Var_A "v1;v2;v3")
# 等效于其它几种形式
set(Var_A v1 v2 v3)
set(Var_A v1;v2;v3)
set(Var_A "v1" "v2" "v3")

在变量列表被整体访问时也会得到使用;进行分隔的整体,例如

message("Var_A=${Var_A}")
#Var_A=v1;v2;v3

下面这种形式不是列表,空格被保存在了Var_B的值中,不视作字符串列表的分隔符

set(Var_B "v1 v2 v3")
message("Var_B=${Var_B}")
#Var_B=v1 v2 v3

可以使用unset命令撤销变量的定义,或者可以把变量修改为空字符串,效果一样(CMake对于未定义变量的解析结果就是空字符串)

unset(Var)
set(Var "")
message("Var=${Var}") # Var=

CMake非常反人类的语法是,如果Var这个变量没有定义,那么直接读取 V a r 也不会报错,会正常地返回一个空字符串。特别注意,在后面的 I F 语句等结构中, I F 后面可以直接使用变量名 V a r ,而不是用 {Var}也不会报错,会正常地返回一个空字符串。 特别注意,在后面的IF语句等结构中,IF后面可以直接使用变量名Var,而不是用 Var也不会报错,会正常地返回一个空字符串。特别注意,在后面的IF语句等结构中,IF后面可以直接使用变量名Var,而不是用{Var},这个语法糖是因为历史原因,IF语句的出现比${}还要早。
可以使用下面的语句来判断是否定义了某个普通变量,如果没有则使用默认值来定义

if(NOT DEFINED ABC)    
set(ABC ON)
endif()

普通变量具有独立的作用域,父作用域的普通变量在子作用域中可以访问,但是在子作用域中的修改不会反馈到父作用域中(可以通过PARENT_SCOPE选项反馈到上一层作用域),详见函数的普通变量。

2.5.2> 缓存变量

缓存变量介绍
CMake第一次构建时会生成CMakeCache.txt,基于CMakeCache.txt存储缓存变量。 此后的构造则会偷懒跳过很多步骤,直接从CMakeCache.txt获取缓存变量。(如果缓存错误,可以直接修改或删除CMakeCache.txt,也可以删除整个build文件夹,让CMake一切重新开始)
缓存变量的常见类型:
BOOL: 布尔值ON/OFF
FILEPATH: 文件路径
PATH: 目录路径
STRING: 字符串
缓存变量的创建和修改:
在生成构建系统的cmake命令中,附加的-D可以直接定义或修改缓存变量,或者使用-U撤销缓存变量,包括最常见的两个缓存变量的设置
CMAKE_BUILD_TYPE 编译类型(Debug/Release等)
CMAKE_INSTALL_PREFIX 安装目录前缀
在CMakeLists中
使用set(…CACHE…)定义缓存变量
注意只能在缓存变量不存在时定义(相当于提供缓存变量的默认值)
如果缓存变量已经存在于CMakeCache.txt中,则这条命令没有修改能力,被直接忽略
在CMakeLists中使用set(…CACHE…FORCE)强制定义或修改缓存变量,此时无论缓存变量是否存在,都会生效
简单粗暴的方式:直接编辑CMakeCache.txt来定义或修改缓存变量(这也是有效的,只需要保证正确的数据格式)
缓存变量的获取:(全局可见)
使用 V a r 获取名为 V a r 的普通变量 / 缓存变量,如果存在恰好同名的普通变量,会优先于缓存变量被读取(因为普通变量是局部的,访问优先级更高)使用 i f ( D E F I N E D V a r ) 也一样,如果定义了名为 V a r 的普通变量或缓存变量都可以使用 {Var}获取名为Var的普通变量/缓存变量,如果存在恰好同名的普通变量,会优先于缓存变量被读取(因为普通变量是局部的,访问优先级更高) 使用if(DEFINED Var)也一样,如果定义了名为Var的普通变量或缓存变量都可以 使用 Var获取名为Var的普通变量/缓存变量,如果存在恰好同名的普通变量,会优先于缓存变量被读取(因为普通变量是局部的,访问优先级更高)使用if(DEFINEDVar)也一样,如果定义了名为Var的普通变量或缓存变量都可以使用CACHE{Var}强制获取名为Var的缓存变量,不会被同名的普通变量替代
缓存变量的意义: cmake可以记住第一次通过命令行-D选项定义的变量和值(通过命令行定义的变量自动是缓存变量),使得第一次生成之后不需要重复输入繁琐的命令,再次生成时不需要重复定义。 再次生成时使用命令行-D选项可以赋值或修改已存在的缓存变量,赋值和修改都在CMakeCache,txt文件中进行。保证CMakeLists中的直接赋值比命令行-D有着更低的修改权限(从机制上避免出现虽然用-D修改,又因为CMake执行CMakeLists中的命令,把-D的修改自动抹掉的情况),但是仍然提供了缓存变量修改的最高权限——通过CMakeLists中的FORCE修改。
缓存变量还可以进一步分为:
外部缓存变量(EXTERNAL cache entries)
CMake创建的外部缓存变量
用户创建的外部缓存变量
内部缓存变量(INTERNAL cache entries)
可以直观地在CMakeCache.txt文件中发现,确实是按照上述结构进行的存储, 例如CMAKE_BUILD_TYPE就是CMake创建的外部缓存变量。
不太了解它们之间的区别,可能只是来源?或者对于外部的缓存变量,CMake可能更加关注,比如有没有使用等。
命令行方式
在命令行中,使用-D选项创建缓存变量的具体语法和例子如下,可以附带缓存变量的类型

cmake -Bbuild -D <var>:<type>=<value>, <var>=<value>
cmake -Bbuild -DCMAKE_BUILD_TYPE=Release
cmake -Bbuild -DMY_CACHE_VAR:STRING=1 -DVAR:BOOL=ON

可以使用-U选项删除缓存文件中的缓存变量,支持按照匹配模式批量删除(但是得留意CMakeLists是不是又自动加进来了)

cmake .. -U <globbing_expr>

CMakeLists方式
在CMakeLists中使用set(…CACHE…)创建缓存变量的语法为

set(<variable> <value> ... CACHE <type> <docstring> [FORCE])

其中的CACHE是必要的,是缓存变量的描述语句,代表缓存变量的类型。
例如

set(MY_CACHE_VALUE "value" CACHE STRING "Value Created by Me")
message("MY_CACHE_VALUE: ${MY_CACHE_VALUE}")

在CMakeLists中使用set(…CACHE…FORCE)命令强制修改缓存变量的值,例如

set(MY_CACHE_VALUE "value" CACHE STRING "Value Created by Me 2" FORCE)
message("MY_CACHE_VALUE: ${MY_CACHE_VALUE}")

还有一个很常用的option命令,它是定义BOOL类型的缓存变量的语法糖

option(<variable> "<help_text>" value)
# 相当于
set(<variable> value CACHE BOOL "<help_text>")
# 例如
option(TEST_DEBUG "option for debug" ON)
2.5.3> 环境变量

cmake可以使用或修改当前进程中是环境变量,环境变量的修改是临时性的,只在当前的cmake进程中有效,建议不要乱用环境变量。

## 设置环境变量
set(ENV{DEMO_VAR} "hello")
## 获取环境变量
$ENV{DEMO_VAR}

和缓存变量不同,无法在缺省ENV关键词的情况下获取到环境变量的值。
撤销环境变量
unset(ENV{DEMO_VAR})
例如获取一个系统预设的环境变量HOME,得到的结果为

if(DEFINED ENV{HOME})    
	message("HOME=$ENV{HOME}") 
	# HOME=/home/username
endif()

2.6> 常见变量

2.6.1> 版本号
PROJECT_VERSION  ## 完整的版本号
PROJECT_VERSION_MAJOR ## 主版本号,第一个数字
PROJECT_VERSION_MINOR ## 第二个数字
PROJECT_VERSION_PATCH ## 第三个数字
PROJECT_VERSION_TWEAK ## 第四个数字
2.6.2> 目录相关变量
## cmake查找.cmake模块的目录,可以使得include命令不需要添加搜索目录
CMAKE_MODULE_PATH  
## cmake安装位置前缀
CMAKE_INSTALL_PREFIX  
## 布尔变量,表明当前的安装位置前缀是否被设置,还是仍然为默认值
CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT  
## 当前项目名称,CMAKE_PROJECT_NAME: 根项目名称;
PROJECT_NAME  
## 项目的编译目录,通常为生成时指定的/build子目录(即:执行cmake -S . -B build/linux  命令中的build/linux路径),三者的细微区别由前缀体现。
PROJECT_BINARY_DIR, <projectname>_BINARY_DIR, CMAKE_BINARY_DIR  
## 项目的源文件目录,通常为project命令的CMakeLists.txt所在目录,三者的细微区别由前缀体现。(建议不要使用CMAKE_SOURCE_DIR)
PROJECT_SOURCE_DIR, <projectname>_SOURCE_DIR, CMAKE_SOURCE_DIR  
## 正在处理的 CMakeLists.txt 所在目录,两者可能略有区别,建议使用后者,尤其在依赖管理时的目录
CMAKE_CURRENT_SOURCE_DIR, CMAKE_CURRENT_LIST_DIR  

说明:

PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行cmake命令的目录,通常为${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

注:上文中不同前缀对应的细微区别如下
CMAKE_ 通常指的是根项目的属性,建议不要直接使用,因为这使得根项目无法作为子项目存在。(根项目指的是启动CMake时的CMakeLists.txt的第一个项目)
PROJECT_ 当前项目的属性,如果只有一个项目,则与CMAKE_相同;如果存在子项目则不同。(当前项目指的是最近一次调用的project(…))
_ 指定某个具体的项目的属性

2.6.3> 特性相关变量
## 编译器的ID,例如"MSVC","GNU","Clang"
CMAKE_CXX_COMPILER_ID
##构建系统
CMAKE_GENERATOR
## 构建模式,debug/release等
CMAKE_BUILD_TYPE
## c++标准,例如20代表c++20
CMAKE_CXX_STANDARD
布尔变量,是否严格要求满足c++标准
CMAKE_CXX_STANDARD_REQUIRED
## debug模式下会给生成的库赋予额外的后缀,便于区分,例如set(CMAKE_DEBUG_POSTFIX "_d")
CMAKE_DEBUG_POSTFIX

3) add_compile_options 配置编译选项

add_compile_options(-Wall -Wextra -pedantic -Werror)

通过命令add_compile_options命令可以为所有编译器配置编译选项(同时对多个编译器生效);

4) add_definitions 添加全局宏定义

add_definitions(-DDEBUG -D GD_TEST)

通过命令add_definitions可以添加全局的宏定义,在源码中就可以通过判断不同的宏定义实现相应的代码逻辑。

5) message 打印信息

语法是:

MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)

(none): 等同于STATUS,但不推荐使用。
STATUS: 输出的信息会被发送到CMake的状态消息流,这是message()函数的默认模式。在命令行上,这些消息通常会被显示出来,但在图形界面中,它们可能会被重定向到其他地方。
WARNING: 输出的信息会被发送到CMake的警告消息流。这些消息会被显示出来,并且会标记为警告。
AUTHOR_WARNING: 这是WARNING模式的一种变体,只有在CMAKE_SUPPRESS_DEVELOPER_WARNINGS变量为FALSE时才会产生警告。
SEND_ERROR: 输出的信息会被发送到CMake的错误消息流,但不会立即停止CMake的处理过程。
FATAL_ERROR: 输出的信息会被发送到CMake的错误消息流,并立即停止CMake的处理过程。

5.1>STATUS 或 (none)

5.1.1> 输出变量的值
set(MY_VARIABLE "Hello, CMake!")
message(STATUS "MY_VARIABLE is: ${MY_VARIABLE}")
## MY_VARIABLE is: Hello, CMake!
5.1.2> 输出配置信息
option(USE_MY_LIBRARY "Use my library" ON)
message(STATUS "USE_MY_LIBRARY is set to: ${USE_MY_LIBRARY}")
## 内容为"USE_MY_LIBRARY is set to: ON"或"USE_MY_LIBRARY is set to: OFF"
5.1.3> 输出构建目标信息
add_library(MyLibrary SHARED src/my_library.cpp)
message(STATUS "Added shared library target: MyLibrary")
## 内容为"Added shared library target: MyLibrary"

5.2> WARNING

在CMake中,message(WARNING "message text")函数用于输出警告消息。这些消息会被发送到CMake的警告消息流,并在终端中显示出来。同时,这些消息会被标记为警告,这意味着它们可能会引起用户的注意,或者在某些情况下,可能会导致构建过程失败。

例如:
假设你正在编写一个需要某个库才能正常工作的项目,你可以使用find_package()函数来查找这个库。如果find_package()函数没有找到这个库,你可以使用message(WARNING)函数来输出一个警告消息,告诉用户这个库没有找到:

find_package(SomeLibrary)
	if(NOT SomeLibrary_FOUND)    
	message(WARNING "SomeLibrary not found, some features will not be available.")
endif()

在这个例子中,如果CMake在配置过程中没有找到SomeLibrary,它会输出一个警告消息,内容为"SomeLibrary not found, some features will not be available."。这个警告消息会被发送到CMake的警告消息流,并在终端中显示出来,从而让用户知道他们可能需要安装SomeLibrary才能使用所有的功能。
请注意,message(WARNING)函数只是输出警告消息,它不会改变CMake的处理过程。如果你想在发生错误时停止CMake的处理过程,你应该使用message(FATAL_ERROR)函数。

5.3> AUTHOR_WARNING

AUTHOR_WARNING模式在CMake中用于输出开发者警告。这种模式的警告只有在CMAKE_SUPPRESS_DEVELOPER_WARNINGS变量为FALSE时才会显示。这个变量默认为FALSE,但如果你在CMakeLists.txt文件中设置了这个变量为TRUE,那么AUTHOR_WARNING模式的警告就不会显示。
这种机制可以用于区分开发者警告和用户警告。例如,如果你是一个库的开发者,你可能希望在开发过程中看到所有的警告,包括开发者警告,但是你的用户可能只关心他们需要知道的警告,不希望看到开发者警告。这时,你就可以在你的CMakeLists.txt文件中使用AUTHOR_WARNING模式来输出开发者警告,然后告诉你的用户设置CMAKE_SUPPRESS_DEVELOPER_WARNINGS变量为TRUE来隐藏这些警告。
以下是一个AUTHOR_WARNING模式的示例:

# 如果某个变量没有被设置,输出一个开发者警告
if(NOT DEFINED MY_VARIABLE)  
	message(AUTHOR_WARNING "MY_VARIABLE is not defined")
endif()

在这个示例中,如果MY_VARIABLE变量没有被设置,CMake会输出一个开发者警告,内容为"MY_VARIABLE is not defined"。如果CMAKE_SUPPRESS_DEVELOPER_WARNINGS变量被设置为TRUE,这个警告就不会显示。

5.4> SEND_ERROR

SEND_ERROR模式在message()函数中用于输出错误信息,但不会立即停止CMake的处理过程。这意味着CMake会继续处理剩余的命令,直到遇到一个FATAL_ERROR或者处理完所有命令。然后,CMake会以非零状态退出,表示出现了错误。
以下是一个SEND_ERROR的示例:

if(NOT DEFINED REQUIRED_VARIABLE)    
	message(SEND_ERROR "REQUIRED_VARIABLE is not defined")
endif()

在这个示例中,我们首先检查变量REQUIRED_VARIABLE是否已经定义。如果没有定义,我们就使用message(SEND_ERROR …)输出一条错误信息。这条错误信息会被发送到CMake的错误消息流,并且CMake的处理过程不会立即停止。相反,CMake会继续处理剩余的命令,直到遇到一个FATAL_ERROR或者处理完所有命令。然后,CMake会以非零状态退出,表示出现了错误。
请注意,虽然SEND_ERROR不会立即停止CMake的处理过程,但它会阻止生成步骤的执行。也就是说,如果CMake在处理过程中遇到了一个SEND_ERROR,那么即使CMake成功处理了所有命令,生成步骤也不会执行。这是因为SEND_ERROR表示了一个严重的问题,需要用户的注意和修复。

5.5> FATAL_ERROR

FATAL_ERROR是CMake中message()函数的一种模式,用于输出错误消息并立即停止CMake的处理过程。当CMake遇到一个无法继续的错误时,你可以使用message(FATAL_ERROR “error message”)来输出错误消息并停止处理。
例如,假设你正在编写一个需要C++11或更高版本的项目,你可以使用以下代码来检查C++编译器是否支持C++11:

if(NOT CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 11)
    message(FATAL_ERROR "This project requires C++11 or higher!")
endif()

6) file 获取文件的指令

file 命令主要用于文件和目录的操作,包括文件操作、路径操作、目录操作等。以下是 file 命令的一些常见用法:

6.1> GLOB获取文件列表

获取匹配指定通配符模式的文件列表,并将结果存储到变量中。

file(GLOB <variable> [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS][<globbing-expressions>...]) 

GLOB: 用于获取匹配指定通配符模式的文件
variable: 一个变量名,用于存储匹配的文件路径。
LIST_DIRECTORIES true|false(可选参数):指定是否列出匹配的文件所在的目录。默认值为 false,即不列出目录。如果设置为 true,则会在返回的文件路径中保留目录信息。
RELATIVE (可选参数):指定相对于哪个路径进行文件搜索。如果没有指定 ,则默认搜索当前 CMakeLists.txt 所在的目录。
CONFIGURE_DEPENDS(可选参数):在生成的 Makefile 中添加规则,以便在重新配置时重新执行 file(GLOB) 命令以更新匹配的文件列表。这可以确保在文件发生变化时重新构建相关的目标。
globbing-expressions(可选参数):
一个或多个用于定制文件搜索的 globbing 表达式。这些表达式可以包含通配符(如 *、? 等)以及其他特定的匹配规则。如果没有指定 globbing 表达式,则默认查找所有文件。
例子:

file(GLOB SRCS ${CMAKE_SOURCE_DIR}/src/*.cpp     
${CMAKE_SOURCE_DIR}/Unittest/*.cpp )  

6.2> GLOB_RECURSE 获取文件列表

获取匹配指定通配符模式的文件列表,并将结果存储到变量中。

file(GLOB_RECURSE <variable> [FOLLOW_SYMLINKS] [LIST_DIRECTORIES true|false] [RELATIVE <path>][CONFIGURE_DEPENDS] [<globbing-expressions>...])

GLOB模式只会在指定的目录搜索文件,不会递归搜索子目录,而GLOB_RECURSE与GLOB类似,但是GLOB_RECURSE不仅会搜索指定目录,连指定目录下的子目录也会递归搜索。

6.3> MAKE_DIRECTORY 创建给定的目录

根据需要创建给定的目录及其父目录。

file(MAKE_DIRECTORY [<directories>...])

directorie:s要创建的目录路径列表。可以指定多个目录路径,以逗号分隔
代码示例:

file(MAKE_DIRECTORY my_directory)

使用该命令来确保所需的目录已经存在,以便后续的操作可以在这些目录中进行。

6.4> REMOVE 和REMOVE_RECURSE 删除给定的文件

file(REMOVE [<files>...]) 
file(REMOVE_RECURSE [<files>...])

REMOVE_RECURSE和REMOVE模式的区别:
REMOVE模式只能删除文件,不能删除目录;REMOVE_RECURSE模式不仅能删除文件,也能删除目录,并且递归删除给定的目录(参数files 可以是目录)。

6.5> RENAME 重命名一个文件或者目录

file(RENAME <oldname> <newname> [RESULT <result>] [NO_REPLACE])

重命名一个文件或者目录。如果没有添加NO_REPLACE选项,如果路径已经存在,则覆盖。
RESULT :3.21版本新增的功能,如果成功,将变量设置为0,否则将显示错误消息。如果未指定RESULT且操作失败,则会触发错误。
NO_REPLACE:3.21版本新增的功能,如果路径已经存在,不需要替换。如果使用RESULT < result >,则result变量将设置为NO_REPLACE。否则,就会触发错误。

6.6> COPY_FILE 复制

复制一个文件从到。不支持目录。

file(COPY_FILE <oldname> <newname> [RESULT <result>] [ONLY_IF_DIFFERENT] [INPUT_MAY_BE_RECENT])

RESULT :如果成功,将变量设置为0,否则将显示错误消息。如果未指定RESULT且操作失败,则会触发错误。
ONLY_IF_DIFFERENT:如果路径已经存在,如果文件的内容已经与相同,则不要替换它(这可以避免更新的时间戳)。
INPUT_MAY_BE_RECENT:3.26版本新增功能,告诉CMake输入文件可能是最近创建的。这只在Windows上有意义,因为在Windows上,文件创建后可能短时间内无法访问。有了这个选项,如果权限被拒绝,CMake将重试几次输入。

6.7> COPY和INSTALL 复制符号链接

将文件、目录和符号链接复制到目标文件夹

file(COPY [...]) 
file(INSTALL [...])

将文件、目录和符号链接复制到目标文件夹。复制可以保留输入文件的时间戳,并优化目标文件中具有相同时间戳的文件。除非明确的权限或NO_SOURCE_PERMISSIONS(默认值为USE_SOURCE_PERMISSIONS),否则复制将保留输入权限。

6.8> SIZE 确定filename的大小

file(SIZE <filename> <variable>)

3.14版本新增加的功能;确定filename的大小,并且将结果存放到variable中。

6.9> READ_SYMLINK 读取链接本身文件

file(READ_SYMLINK <linkname> <variable>)

3.14版本新增的功能;查询符号链接,并将其指向的路径存储中;它会获取符号链接的最终目标路径,而不是符号链接本身。如果不存在或不是符号链接,CMake将发出致命错误。
READ_SYMLINK模式返回的路径是可能是相对路径,也可能是绝对路径

set(linkname "/path/to/foo.sym") 
file(READ_SYMLINK "${linkname}" result)
#判断result是否为绝对路径,IS_ABSOLUTE 是一个函数,用于判断一个路径是否是绝对路径。  
if(NOT IS_ABSOLUTE "${result}")     
	#获取linkname的目录部分
	get_filename_component(dir "${linkname}" DIRECTORY)
	#进目录部分与相对路径合并,即为绝对路径       	
	set(result "${dir}/${result}") 
endif()

6.10> CREATE_LINK 创建一个链接

file(CREATE_LINK <original> <linkname> [RESULT <result>] [COPY_ON_ERROR] [SYMBOLIC])

3.14版本新增功能;创建一个链接指向。默认情况下,它将是一个硬链接,但提供SYMBOLIC选项将创建为符号链接。硬链接要求original存在并且是文件,而不是目录。如果已经存在,它将被覆盖。
RESULT :
如果指定了变量,则接收操作的状态。成功时将其设置为0,否则将显示错误消息。如果未指定RESULT且操作失败,则会触发致命错误。
COPY_ON_ERROR:
会在创建链接失败时将文件作为回退复制。它对于处理以下情况很有用:例如和在不同的驱动器或挂载点上,这使得它们无法支持硬链接。

6.11> CHMOD 赋权限

file(CHMOD <files>... <directories>... [PERMISSIONS <permissions>...] [FILE_PERMISSIONS <permissions>...][DIRECTORY_PERMISSIONS <permissions>...]) 
file(CHMOD_RECURSE <files>... <directories>... [PERMISSIONS <permissions>...] [FILE_PERMISSIONS <permissions>...] [DIRECTORY_PERMISSIONS <permissions>...])

3.19版本新增的功能;设置指定的files和directories权限,有效权限为:OWNER_READ、OWNER_WRITE、OWNER_EXECUTE、GROUP_READ、GROUP_WRITE、GROUP_EXECUTE、WORLD_READ、WORLD_WRITE、WORLD_EXECUTE、SETUID、SETGID。
参数:
PERMISSIONS:
改变所有项的权限,files和directories。
FILE_PERMISSIONS:
只改变文件项权限;
DIRECTORY_PERMISSIONS:
只改变目录项权限;
当这三个参数同时存在时有一定的优先级关系:对目录操作DIRECTORY_PERMISSIONS覆盖PERMISSIONS,对文件操作FILE_PERMISSIONS覆盖PERMISSIONS,DIRECTORY_PERMISSIONS和FILE_PERMISSIONS同时存在类似于PERMISSIONS;
CHMOD_RECURSE模式与CHMOD类似,只不过CHMOD_RECURSE会递归改变子文件权限。

6.12> REAL_PATH 路径转换

file(REAL_PATH <path> <out-var> [BASE_DIRECTORY <dir>] [EXPAND_TILDE])

计算已解析符号链接的现有文件或目录的绝对路径。
BASE_DIRECTORY :如果提供的 是一个相对路径,它会相对于给定的基本目录

进行计算。如果没有提供基本目录,默认的基本目录将是CMAKE_CURRENT_SOURCE_DIR。
EXPAND_TILDE:3.21版本新增的功能;如果<路径>是 或以/开头,~将被用户的主目录替换。home目录的路径从环境变量中获取。

6.13> RELATIVE_PATH 取相对于目录的相对路径

file(RELATIVE_PATH <variable> <directory> <file>)

用于确定文件相对于目录的相对路径,并将其存储在变量中。
variable:一个变量名,用于存储相对路径。
directory:一个目录路径,作为参考路径。
file:一个文件路径,相对于参考目录的相对路径将被计算并存储在变量中。
示例代码:

set(CMAKE_DIR "/home/project")   
set(SOURCE_DIR "/home/project/resources")      
file(RELATIVE_PATH relative_path ${CMAKE_DIR} ${SOURCE_DIR}/file.txt)   
message("myfile.txt relative path: ${relative_path}") 

6.14> TO_CMAKE_PATH 和TO_NATIVE_PATH

file(TO_CMAKE_PATH "<path>" <variable>) 
file(TO_NATIVE_PATH "<path>" <variable>)

CMake 中的两个函数,用于将文件路径从一种表示法转换为另一种表示法。
path:要转换的文件路径。
variable:一个变量名,用于存储转换后的 CMake 路径表示法。
TO_CMAKE_PATH:
用途是将文件路径从其他表示法转换为 CMake 的路径表示法。CMake 的路径表示法使用 / 作为路径分隔符,可以在不同操作系统上使用,而不受操作系统特定的路径分隔符的限制。
代码示例:

set(MY_FILE_PATH "C:\\Users\\user\\file.txt")   
file(TO_CMAKE_PATH "${MY_FILE_PATH}" cmake_path)   
message("file.txt (CMake format): ${cmake_path}")
 #运行结果 file.txt (CMake format): C:/Users/user/file.txt

TO_NATIVE_PATH:
将文件路径从 CMake 的路径表示法转换为特定操作系统的本地路径表示法。
代码示例:

set(MY_CMAKE_PATH "/cygdrive/c/Users/file.txt")   file(TO_NATIVE_PATH "${MY_CMAKE_PATH}" native_path)   message("file.txt (Windows format): ${native_path}")

6.15 DOWNLOAD和UPLOAD 上传下载

用于在本地计算机和远程计算机之间进行文件下载和上传操作

file(DOWNLOAD <url> [<file>] [<options>...]) 
file(UPLOAD <file> <url> [<options>...])

CMake 中的两个函数,用于在本地计算机和远程计算机之间进行文件下载和上传操作。DOWNLOAD用于从指定的 URL 下载文件到本地计算机;UPLOAD用于将指定的文件上传到远程计算机;
url:是要下载的文件的 URL,
file:是要保存的文件路径(如果未指定,则由 CMake 自动生成一个临时文件路径),
options:是可选的参数,用于指定下载过程中的一些行为,例如是否在下载之前删除已存在的文件、是否显示下载进度等。选项如下:

6.16 LOCK锁(Locking)

file(LOCK <path> [DIRECTORY] [RELEASE] [GUARD <FUNCTION|FILE|PROCESS>] [RESULT_VARIABLE <variable>] [TIMEOUT <seconds>])

3.18新增的功能;这个函数用于在文件系统上获取一个文件的锁。它接受一系列参数来指定锁的类型、行为和选项。下面是每个参数的说明:
LOCK :
指定要锁定的文件的路径。
DIRECTORY:
如果指定了该参数,则文件锁将应用于目录而不是文件。这个选项用于在目录上设置读锁或写锁,以控制对该目录的访问。
RELEASE:
如果指定了该参数,则在函数执行完毕后自动释放文件锁。这个选项用于在需要手动控制锁的释放时使用。
GUARD<FUNCTION|FILE|PROCESS> :
如果指定了该参数,则文件锁将作为保护措施,在指定的函数、文件或进程范围内一直保持有效。这个选项用于确保在特定的代码块或进程中只有一个实例可以访问受保护的资源。
RESULT_VARIABLE :
如果指定了该参数,则将函数的结果存储在指定的变量中。这个选项用于获取文件锁操作的结果状态。
TIMEOUT :
如果指定了该参数,则设置文件锁的超时时间。如果文件锁在指定的时间内无法获取,则函数将返回失败。
这个函数在获取文件锁时可能会阻塞程序的执行,直到获得锁为止。超时参数可以用来限制等待时间,以避免长时间阻塞程序。

6.17. ARCHIVE_CREATE 归档

file(ARCHIVE_CREATE OUTPUT <archive> PATHS <paths>... 
[FORMAT <format>] [COMPRESSION <compression> 
[COMPRESSION_LEVEL <compression-level>]] [MTIME <mtime>] [VERBOSE])

3.18新增的功能;用于创建归档文件的 CMake 命令。它通过将多个文件和目录组合在一起,创建一个归档文件,以便于存储、传输和备份,就是所谓的打包,压缩。下面是该命令的参数说明:
OUTPUT :
指定要创建的归档文件的名称或路径。
PATHS …:
指定要打包的文件或文件路径列表。可以包含多个路径,以逗号分隔。
FORMAT :
可选参数,指定归档文件的格式。例如,常见的格式包括 ZIP、GZIP、BZIP2 等。
COMPRESSION :
可选参数,指定压缩算法。可以和上面的 FORMAT 参数一起使用,以指定特定的归档格式和压缩算法。例如,COMPRESSION ZIP 表示使用 ZIP 格式进行压缩。
COMPRESSION_LEVEL :
可选参数,指定压缩级别。如果使用了压缩算法,可以通过这个参数来指定压缩的程度。常见的压缩级别包括高速(fastest)和最大(best),以及一些介于两者之间的级别。
MTIME :
可选参数,指定归档文件的修改时间。可以使用一个时间戳或一个日期来指定。
VERBOSE:
可选参数,如果启用,将显示详细的命令输出信息。
示例程序:

file(ARCHIVE_CREATE OUTPUT test.tar.gz PATHS build FORMAT GZIP)
#将build文件夹以gzip格式进行压缩,压缩包名为test.tar.gz 

6.18 ARCHIVE_EXTRACT 解压

file(ARCHIVE_EXTRACT INPUT <archive> [DESTINATION <dir>] [PATTERNS <patterns>...] [LIST_ONLY] [VERBOSE] [TOUCH])

用于从归档文件中提取文件或文件路径,就是所谓的解压。以下是参数说明:
INPUT :
指定要提取的归档文件的名称或路径。
DESTINATION


可选参数,指定提取的文件或文件路径的输出目录。如果未指定,则输出到当前工作目录。
PATTERNS …:
可选参数,指定要提取的文件或文件路径的模式列表。可以使用多个模式,以逗号分隔。模式可以是通配符,例如 /.txt 表示提取所有子目录中的所有 .txt 文件。
LIST_ONLY:
可选参数,如果指定了该参数,则只列出要提取的文件或文件路径,而不实际提取它们。
VERBOSE:
可选参数,如果指定了该参数,则显示详细的命令输出信息。
TOUCH:
可选参数,如果指定了该参数,则仅更新提取的文件的修改时间,而不实际提取它们。

file(ARCHIVE_EXTRACT INPUT test.tar.gz ./)
#将test.tar.gz 解压到当前目录

7) 字符串操作

make支持对字符串的简单操作。在这里我们使用表示字符串值,使用表示值为字符串的变量名称。
以如下的长字符串为例

set(S"Pride and Prejudice is kind of a literary Rosetta Stone, the inspiration, basis, and model for so many modern novels. \You’re probably more familiar with its plot and characters than you think. \For a book written in the early 19th century, it’s modernity is surprising only until you realize that this is the novel that in many ways defined what a modern novel is.")
message("S: ${S}")

字符串访问与查找

7.1> LENGTH 获取字符串的长度

获取字符串的长度,结果存在out-var中

string(LENGTH <string> <out-var>)

注意这里不是给一个字符串变量,而是需要给一个字符串值,例如

set(S2 "abc e ")
string(LENGTH ${S2} N)
message("N=${N}")
# N=6

7.2> FIND 查找

在字符串中查找指定的子串,返回子字符串开头在原字符串中的索引,默认查找第一次出现的,也可以反向查找最后一次出现的,没有找到会返回-1

string(FIND <string> <substring> <out-var> [...])
string(FIND <string> <substring> <output_variable> [REVERSE])

例如

string(FIND ${S} "in" S_index)
string(FIND ${S} "in" S_index1 REVERSE)
message("S_index=${S_index},S_index1=${S_index1}")
# S_index=24,S_index1=339
# 第一个in是单词kind中的
# 最后一个in是单词defined中的

7.3> SUBSTRING子字符串提取

子字符串提取,指定字串的开始索引和长度,结果存入out-var

string(SUBSTRING <string> <begin> <length> <out-var>)

例如

string(SUBSTRING ${S} 0 8 S_HEAD)message("S_HEAD=${S_HEAD}")
# S_HEAD=Pride an

7.4> APPEND 尾部添加

在字符串变量的尾部添加字符串

string(APPEND <string-var> [<input>...])

例如

set(S2 "Hello")
string(APPEND S2 " Stone")
message("S2=${S2}")# S2=Hello Stone

7.5> PREPEND 头部添加

在字符串变量的头部添加字符串

string(PREPEND <string-var> [<input>...])

例如

set(S2 "Hello")
string(PREPEND S2 "Stone ")
message("S2=${S2}")
# S2=Stone Hello

7.6> REPLACE字符串替换

将输入字符串中所有出现的替换为<replace_string>,并将修改后的结果存储在<output_var>中。

string(REPLACE <match-string> <replace-string> <out-var> <input>...)

例如

set(S2 "Hello,world!")
string(REPLACE "!" "?" S2_M ${S2})
message("S2_M=${S2_M}")
# S2_M=Hello,world?

字符串正则表达式替换
^: 匹配输入开头
$: 匹配输入结束
.: 匹配任意单个字符
<char>: 匹配单字符。使用它来匹配特殊的正则表达式字符,例如 . 表示点,\ 表示反斜杠,\a 表示 a
[ ]: 匹配任何在括号内的字符
[^ ]: 匹配任何不在括号内的字符
-: 用在方括号内,指定字符的范围,例如[a-f]表示[abcdef],[0-3]表示[0123],[+*/-] 表示数学运算符。
*: 匹配前面模式的零次或多次
+: 匹配前面模式的一次或多次
?: 匹配前面模式的零次或一次
|: 匹配 | 两侧的模式
(): 保存匹配的子表达式(模式)

7.7> REGEX MATCH 字符串正则匹配

将所有输入字符串在匹配之前都连接在一起,然后根据正则表达式<regular_expression>匹配一次,将匹配的结果存储在<output_variable>

string(REGEX MATCH <regular_expression> <output_variable> <input> [<input>...])

例如可以匹配任何含有in的单词,但是注意到只会匹配一次

string(REGEX MATCH "[A-Za-z]*in[A-Za-z]*" S_out_var ${S})
message("S_out_var=${S_out_var}")
# S_out_var=kind

7.8> REGEX MATCHALL 字符串正则匹配

和7.7的区别就是匹配所有的项,结果以一个列表的形式返回

string(REGEX MATCHALL <regular_expression> <output_variable> <input> [<input>...])

例如可以匹配任何含有in的所有单词

string(REGEX MATCHALL "[A-Za-z]*in[A-Za-z]*" S_out_var ${S})
message("S_out_var=${S_out_var}")
# S_out_var=kind;inspiration;think;in;surprising;in;defined

7.9> REGEX REPLACE 字符串正则替换

将所有输入字符串在匹配之前都连接在一起,然后尽可能匹配<regular_expression>并替换为 <replacement_expression>,将结果存储在<output_variable>。

string(REGEX REPLACE <regular_expression> <replacement_expression> <output_variable> <input> [<input>...])

例如把所有匹配到的含有in的单词,替换成hello

string(REGEX REPLACE "[A-Za-z]*in[A-Za-z]*" "hello" S_out_var ${S})

7.10> TOUPPER,TOLOWER 字符串大小写转换

修改字符串的大小写形式,结果存入out-var

string(TOUPPER <string> <out-var>)string(TOLOWER <string> <out-var>)

例如

set(S2 "aBc")string(TOUPPER ${S2} S2_U)string(TOLOWER ${S2} S2_L)
message("S2_U=${S2_U}")
message("S2_L=${S2_L}")
# S2_U=ABC# S2_L=abc

7.11> COMPARE字符串比较

对两个字符串按照字典序列比较,注意对大小写敏感,结果true或false存入最后的变量中

string(COMPARE LESS <string1> <string2> <output_variable>)
string(COMPARE GREATER <string1> <string2> <output_variable>)
string(COMPARE EQUAL <string1> <string2> <output_variable>)
string(COMPARE NOTEQUAL <string1> <string2> <output_variable>)
string(COMPARE LESS_EQUAL <string1> <string2> <output_variable>)
string(COMPARE GREATER_EQUAL <string1> <string2> <output_variable>)

8)列表操作

cmake支持对值为字符串列表的变量(通常是文件名列表,或目录列表)使用简单的操作。
列表访问与查询

8.1> LENGTH 获取列表的长度

会把list的长度赋值给out-var

list(LENGTH <list> <out-var>)

例如

list(LENGTH A Alen)
message("Alen=${Alen}")
# Alen=7

8.2> GET 获取索引的元素

获取列表指定索引的元素,索引从0开始,0代表第一个元素,还支持反向索引,-1代表最后一个元素

list(GET <list> <element index> [<index> ...] <out-var>)

这里既可以只用一个索引,得到的单个值保存在out-var,也可以使用多个索引,得到的值列表保存在out-var,例如

set(A a b c d e)
list(GET A 1 X)
list(GET A -1 -2 Y)
message("X=${X},Y=${Y})
# X=b,Y=e;d

8.3> FIND 查找指定元素

在列表中查找指定元素,返回列表指定元素的索引,如果未找到返回 -1

list(FIND <list> <value> <out-var>)

例如

set(A a b c d e)
list(FIND A d Id)
message("Id=${Id}")
# Id=3
列表增加

8.4> APPEND 尾部增加

在列表尾部增加元素

list(APPEND <list> <values>

例如

set(A a b c d e)list(APPEND A f g)
message("A=${A}")
# A=a;b;c;d;e;f;g

8.5> PREPEND头部添加

在列表的头部添加元素

list(PREPEND <list> [<element>...])

例如

set(A a b c d e)
list(PREPEND A f g)
message("A=${A}")
# A=f;g;a;b;c;d;e

8.6> INSERT 插入元素

在列表指定索引位置插入元素

list(INSERT <list> <index> [<element>...])

例如

set(A a b c d e)
message("A=${A}")
list(INSERT A 0 w)
message("A=${A}")
# A=a;b;c;d;e# A=w;a;b;c;d;e

8.7> REMOVE_ITEM 列表删除

按照值删除,可以同时删除多个值

list(REMOVE_ITEM <list> <value>...)

8.8> REMOVE_AT索引删除

按照索引删除,可以同时删除多个索引

list(REMOVE_AT <list> <index>...)

8.9> REMOVE_DUPLICATES去重

列表去重,保持相对顺序

list(REMOVE_DUPLICATES <list>)

8.10> POP_BACK,POP_FRONT

以栈的形式删除列表的尾部或头部的若干元素(默认只删除一个,但是如果后接m个变量,则一次性删除m个,并且把值赋给它们)

list(POP_BACK <list> [<out-var>...])
list(POP_FRONT <list> [<out-var>...])

9)数学表达式

既然是把cmake作为一门语言,那么基本的数学运算也搞一搞吧。

math(EXPR <var> "<expression>" [OUTPUT_FORMAT <format>])

计算的表达式expression需要以字符串的形式给出,支持数字和运算符(C语言风格),计算的结果会保存到var中,甚至可以加入一些输出格式要求,例如

math(EXPR value "3+2")
message("3+2=${value}")# 3+2=5

甚至可以加上流程控制,达到更复杂的运算

set(value 0)
while(${value} LESS 5)    
math(EXPR value "${value}+1")    
message("value=${value}")
endwhile()
# value=1# value=2# value=3# value=4# value=5

10)find_program 查找程序

CMake中的命令find_program用于查找程序(program),其格式如下:
将创建一个由命名的缓存条目即cache变量,将的值存入CMakeCache.txt中;或如果指定了NO_CACHE,由命名的普通变量来存储此命令的结果,这种情况下,的值将不会存入CMakeCache.txt中。如果找到程序,则结果将存储在中,除非清除,否则不会重复搜索。如果没找到,结果将为-NOTFOUND。

find_program (
          <VAR>
          name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
          [HINTS [path | ENV var]... ]
          [PATHS [path | ENV var]... ]
          [REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
          [PATH_SUFFIXES suffix1 [suffix2 ...]]
          [DOC "cache documentation string"]
          [NO_CACHE]
          [REQUIRED]
          [NO_DEFAULT_PATH]
          [NO_PACKAGE_ROOT_PATH]
          [NO_CMAKE_PATH]
          [NO_CMAKE_ENVIRONMENT_PATH]
          [NO_SYSTEM_ENVIRONMENT_PATH]
          [NO_CMAKE_SYSTEM_PATH]
          [NO_CMAKE_INSTALL_PREFIX]
          [CMAKE_FIND_ROOT_PATH_BOTH |
           ONLY_CMAKE_FIND_ROOT_PATH |
           NO_CMAKE_FIND_ROOT_PATH]
)

注:
a> NAMES:为程序指定一个或多个可能的名字。当使用它来指定带有和不带有版本后缀的名称时,建议首先指定不带版本控制的名字,以便在发行版(distributions)提供的软件包之前找到本地构建的软件包。示例代码段如下:

# 清除变量,带有CACHE也从缓存文件CMakeCache.txt中清除,若不带CACHE则缓存文件CMakeCache.txt中仍然存在var的值
unset(var CACHE) 
find_program(var NAMES cmake)
message("var: ${var}") # var: /usr/bin/cmake
unset(var CACHE)
 # 最简格式:find_program(<VAR> name),查找默认路径
find_program(var gcc)
message("var: ${var}") # var: /usr/bin/gcc

b> HINTS, PATHS:指定除默认位置外还要搜索的目录。"ENV var"子选项从系统环境变量读取路径。注意HINTS与PATHS区别:HINTS是在搜索系统路径之前先搜索HINTS指定的路径。PATHS是先搜索系统路径,然后再搜索PATHS指定的路径

# 手动将/usr/bin/cmake拷贝到/opt/cmake/目录下
unset(var CACHE)
find_program(var NAMES cmake PATHS /opt/cmake) # PATHS:先搜索系统路径,然后再搜索PATHS指定的路径
message("var: ${var}") # var: /usr/bin/cmake
unset(var CACHE)
find_program(var NAMES cmake HINTS /opt/cmake) # HINTS:先搜索HINTS指定的路径,然后再搜索系统路径
message("var: ${var}") # var: /opt/cmake/cma

c> REGISTRY_VIEW:3.24版本中引入。指定必须要查询的注册表视图(registry views)。此选项仅在Windows平台上有意义,在其它平台上将被忽略。
d> PATH_SUFFIXES:若在PATHS或HINTS指定的路径中没有找到,则会在PATHS/PATH_SUFFIXES或HINTS/PATH_SUFFIXES指定的路径中继续搜索。示例代码段如下

unset(var CACHE)
find_program(var NAMES cmake PATHS /opt PATH_SUFFIXES cmake NO_DEFAULT_PATH) # 搜索:(1)/opt; (2)/opt/cmake/
message("var: ${var}") # var: /opt/cmake/cmake

e> DOC:指定由命名的缓存条目的文档字符串,即在CMakeCache.txt文件中会增加对的描述。示例代码段如下:

unset(var CACHE)
find_program(var NAMES cmake DOC "cmake program") # CMakeCache.txt中会对var增加注释说明
message("var: ${var}") # var: /usr/bin/cmake

f> NO_CACHE:3.21版本中引入。搜索结果将存储在普通变量中而不是缓存条目中。如果变量在调用之前已经设置(作为普通变量或缓存变量),则搜索将不会发生。应谨慎使用此选项,因为它会大大增加重复配置步骤的成本。示例代码段如下:

unset(var1 CACHE)
find_program(var1 NAMES cmake NO_CACHE) # 带有NO_CACHE后,var1的值将不会存入CMakeCache.txt中
message("var1: ${var1}") # var1: /usr/bin/cmake

g> REQUIRED:3.18版本中引入。如果未找到任何内容,则停止处理并触发错误消息,否则下次使用相同的变量调用find_program时将再次尝试搜索。示例代码段如下:

unset(var CACHE)
find_program(var NAMES valgrind REQUIRED) # 将触发error,停止后续的执行:CMake Error at test_find_program.cmake:68 (find_program): Could not find var using the following names: valgrind

11)add_subdirectory 构建时构建子目录

add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])

注:
source_dir
必选参数。该参数指定一个子目录,子目录下应该包含CMakeLists.txt文件和代码文件。子目录可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前目录的一个相对路径。
binary_dir
可选参数。该参数指定一个目录,用于存放输出文件。可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前输出目录的一个相对路径。如果该参数没有指定,则默认的输出目录使用source_dir。
EXCLUDE_FROM_ALL
可选参数。当指定了该参数,则子目录下的目标不会被父目录下的目标文件包含进去,父目录的CMakeLists.txt不会构建子目录的目标文件,必须在子目录下显式去构建。例外情况:当父目录的目标依赖于子目录的目标,则子目录的目标仍然会被构建出来以满足依赖关系(例如使用了target_link_libraries)。

12)link_directories 和 target_link_directories

12.1> link_directories

CMake中的link_directories命令用于添加目录使链接器能在其查找库。link_directories用于相对路径,target_link_directories用于绝对路径。

link_directories([AFTER|BEFORE] directory1 [directory2 ...])
添加路径使链接器应在其中搜索库。提供给此命令的相对路径被解释为相对于当前源目录。
该命令只适用于在它被调用后创建的target。
这些目录将添加到当前CMakeLists.txt文件的LINK_DIRECTORIES目录属性中,并根据需要将相对路径转换为绝对路径。
默认情况下,指定的目录将追加到当前目录列表中。可以通过将CMAKE_LINK_DIRECTORIES_BEFORE设置为ON来更改此默认行为。通过显式使用AFTER或BEFORE,你可以在追加和前置之间进行选择,而与默认值无关。
注意:此命令很少是必需的,在有其它选择的情况下应避免使用。在可能的情况下,最好将完整的绝对路径传递给库,因为这可确保始终链接正确的库。find_library命令提供完整路径,通常可以直接用于调用target_link_libraries命令。

例如:

link_directories( ../ThirdLib/lib)

12.2> target_link_directories

target_link_directories(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

必须是已经通过add_executable()或add_library()等命令创建出来的目标,对于每一个添加的目录,也有可选的范围参数PUBLIC、PRIVATE和INTERFACE,区别是:指定PUBLIC或PRIVATE时,目录会添加到目标的LINK_DIRECTORIES属性中;指定PUBLIC或INTERFACE时,目录会添加到目标的INTERFACE_LINK_DIRECTORIES属性中。和link_directories的主要差别是:
link_directories会对当前CMakeLists.txt文件的目标文件生效,并会通过add_subdirectory调用传递到子目录;target_link_directories则针对指定的目标文件生效。
target_link_directories对于指定目标添加的目录,INTERFACE和PUBLIC会添加到的INTERFACE_INCLUDE_DIRECTORIES属性,PUBLIC和PRIVATE会添加到的INCLUDE_DIRECTORIES属性中。
默认情况下,会按照追加的方式添加目录,如果指定了参数BEFORE,那么添加的时候会将目录插入到最前面。
工程结构:

target_link_directories/
├── CMakeLists.txt
├── lib
│   └── libtest.a
└── main.cpp

使用:

add_executable(main main.cpp)
target_link_libraries(main test)
target_link_directories(main PRIVATE lib)

13)target_link_libraries

target_link_libraries既支持链接到CMake的target,也支持连接到一个已经存在的库文件
target_link_libraries(设置要链接的库文件的名称)
指定链接给定目标和/或其依赖项时要使用的库或标志。来自链接库目标的使用要求将被传播。目标依赖项的使用要求会影响其自身源的编译
示例:

target_link_libraries(${PROJECT_NAME} PUBLIC ThirdLib testDir2 testDir3 testDir4)

14)add_library添加创建库

add_library是CMake中的一个核心命令,用于添加库(libraries)到你的项目中。库是一个包含了预编译好的代码的文件,这些代码可以被你的应用程序或其他库所共享和重用。
在CMake中,add_library命令的基本语法如下:

add_library(<name> <SHARED|STATIC|MODULE|INTERFACE> [source1] [source2 ...])

注:
是你要创建的库的名称
<SHARED|STATIC|MODULE|INTERFACE>用于指定库的类型。
add_library可以缺省STATIC|SHARED参数,此时默认为STATIC全部生成静态库。
你可以选择的类型包括SHARED(共享库,Shared Libraries)、STATIC(静态库,Static Libraries)、MODULE(模块库,Module Libraries)或INTERFACE(接口库,Interface Libraries)。[source1] [source2 …]则是构成库的源代码文件。
示例:

## 添加库
## PROJECT_NAME 当前项目名称
add_library(${PROJECT_NAME} SHARED ${SRC_LIST})
## ${PROJECT_NAME}::${TARGET_NAME} 是 ${PROJECT_NAME} 别名
add_library(${PROJECT_NAME}::${TARGET_NAME}  ALIAS  ${PROJECT_NAME})

15)set_target_properties

set_target_properties 的作用是设置目标的属性,可以是目标文件输出的名称或者目录、目标文件的版本号。与之对应的,我们可以使用 get_target_properties 来获取目标文件某一属性对应的值。
命令格式如下:

set_target_properties(目标文件1 目标文件2 ...
                      PROPERTIES 
                      属性1 属性值1 属性2 属性值2 ...)       

示例1:

## 设置构建目标文件的属性
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${TARGET_NAME}")
# 构建一个新的target时,会清理掉其他使用这个名字的库,如果没有清理还是会只会构建一个动态库
set target_properties(${PROJECT_NAME} PROPERTIES CLEAN_DIRECT_OUTPUT 1)

示例2:

set_target_properties(myapp PROPERTIES OUTPUT_NAME "myapp" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")

这将设置可执行文件 myapp 的输出名称为 myapp,输出路径为 ${CMAKE_BINARY_DIR}/bin。

16)aux_source_directory

添加源文件:

file(GLOB_RECURSE SRCS CONFIGURE_DEPENDS src/*.cpp include/*.h)
add_executable(Demo)
target_sources(Demo PRIVATE ${SRCS})

一个替代的命令是下面的aux_source_directory,
它可以自动搜集添加当前目录下的源文件(根据项目使用的语言确定源文件),然后存储到变量中,例如

add_executable(Demo)
aux_source_directory(. SRCS)
aux_source_directory(test SRCS)
target_sources(Demo ${SRCS})

17)add_compile_definitions

添加预定义宏

add_compile_definitions([<definition>...])

可以指定多个宏定义,它们将按顺序添加到编译器中
示例:
#添加多个宏

add_compile_definitions(
    DEF_AGE=20
    DEF_NAME="momo"
    DEF_TEST)

#添加一个宏

add_compile_definitions(DEF_TEST)

18)cmake_minimum_required

例子:

cmake_minimum_required(VERSION 3.0 FATAL_ERROR)

cmake_minimum_required 写在CMakeLists的第一行,表示这个CMakeLists需要的最低版本的CMake,FATAL_ERROR表示,如果达不到最低版本要求就报致命错误,停止执行(CMake的语法变化非常大,因此有必要声明一下最低的版本要求)

19)CMAKE_POSITION_INDEPENDENT_CODE

set(CMAKE_POSITION_INDEPENDENT_CODE ON)

在CMake中,这句话会告诉编译器生成位置无关代码(Position Independent Code, PIC),即编译时使用相对地址,而不是绝对地址。生成PIC的目的是为了使得可执行文件和共享库的代码段可以被映射到任意地址而不会出错,这对于多个进程之间的代码共享非常有用。另外,也可以减小可执行文件和共享库的大小。在Linux和Unix系统中,使用动态链接库时需要使用PIC。在Windows系统中,使用动态链接库时不需要使用PIC,但是在某些情况下,使用PIC可以提高代码的可移植性和安全性。

20) CMAKE_AUTOMOC

set(CMAKE_AUTOMOC ON)

在使用 Qt 框架开发 C++ 代码时,需要使用 moc 工具将一些特殊的 Qt 宏扩展为 C++ 代码。例如,当我们定义了一个带有 Q_OBJECT 宏的类时,moc 工具会在编译期间自动生成一些额外的代码,使得该类能够支持 Qt 的信号槽机制等功能。而将 C++ 代码与 moc 生成的代码进行合并,则需要在 CMake 中添加 set(CMAKE_AUTOMOC ON),以启用自动 moc 机制。这样,CMake 会自动搜索项目中所有需要进行 moc 的文件,并在编译时自动调用 moc 工具生成对应的代码。

21)add_custom_command

2.1 add_custom_command命令的基本语法

基本语法如下:

add_custom_command(
    OUTPUT output1 [output2 ...]
    COMMAND command1 [ARGS] [args1...]
    [COMMAND command2 [ARGS] [args2...] ...]
    [MAIN_DEPENDENCY depend]
    [DEPENDS [depends...]]
    [BYPRODUCTS [files...]]
    [WORKING_DIRECTORY dir]
    [COMMENT comment]
    [VERBATIM]
)

这个命令的主要作用是定义一条自定义的构建规则,这条规则可以在构建过程中执行一系列的命令。

OUTPUT output1 [output2 …]:这个参数用于指定自定义命令的输出文件。这些文件在构建过程中会被生成,如果这些文件不存在,那么CMake就会执行这条自定义命令。
COMMAND command1 [ARGS] [args1…]:这个参数用于指定要执行的命令。你可以提供任何有效的命令,包括系统命令、脚本,或者其他的构建工具。ARGS关键字后面可以跟随一系列的参数,这些参数会被传递给命令。
MAIN_DEPENDENCY depend:这个参数用于指定自定义命令的主要依赖。如果这个依赖的文件被修改,那么自定义命令就会被执行。
DEPENDS [depends…]:这个参数用于指定自定义命令的其他依赖。如果这些依赖的文件被修改,那么自定义命令也会被执行。
BYPRODUCTS [files…]:这个参数用于指定自定义命令的副产品。如果你指定了一个或多个文件作为副产品,那么这些文件将会被添加到构建系统的清理列表中。
WORKING_DIRECTORY dir:这个参数用于指定自定义命令的工作目录。如果你没有指定这个参数,那么自定义命令将会在当前的源码目录中执行。
COMMENT comment:这个参数用于指定一个注释,这个注释将会在执行自定义命令时被打印出来。
VERBATIM:这个参数用于控制命令参数的处理方式。如果你指定了VERBATIM,那么命令参数将会被按照字面意义处理,而不会被解析为变量或表达式。

2.2 add_custom_command命令的主要选项详解

2.2.1 TARGET选项(TARGET Option)

在CMake中,add_custom_command命令的TARGET选项是一个非常重要的参数。它的主要作用是指定一个目标,自定义命令将会在构建这个目标时被执行。这个目标可以是任何CMake支持的目标类型,包括库(Library)、可执行文件(Executable)、测试(Test)等。

使用方法
TARGET选项的使用方法非常简单。在add_custom_command命令中,我们只需要在TARGET后面添加我们想要指定的目标名称即可。例如:

add_custom_command(
TARGET my_target
COMMAND echo “This is a custom command for my_target.”
)
在这个例子中,我们指定了my_target为目标,当我们构建my_target时,会执行echo "This is a custom command for my_target."这个命令。

注意事项
在使用TARGET选项时,有几点需要注意:

TARGET指定的目标必须是已经存在的目标。如果你试图指定一个不存在的目标,CMake会在配置阶段报错。
TARGET选项只能用在add_custom_command命令中,不能用在add_custom_target命令中。这是因为add_custom_target命令是用来创建一个新的目标的,而add_custom_command命令是用来给已经存在的目标添加自定义命令的。
TARGET选项指定的目标,必须是在add_custom_command命令之前定义的。如果你试图在add_custom_command命令之后定义目标,CMake会在配置阶段报错。
实际应用
在实际的项目中,TARGET选项常常被用来添加一些在构建过程中需要执行的额外命令。例如,我们可能需要在构建一个库之前,先生成一些需要的源代码;或者在构建一个可执行文件之后,复制一些资源文件到指定的目录。这些操作都可以通过TARGET选项和add_custom_command命令来实现。

总的来说,TARGET选项是add_custom_command命令中一个非常重要的选项,它让我们可以在构建过程中添加自定义的命令,从而使得构建过程更加灵活和可控。

2.2.2 PRE_BUILD、PRE_LINK 和 POST_BUILD选项

在CMake的add_custom_command命令中,PRE_BUILD、PRE_LINK和POST_BUILD是非常重要的选项,它们用于指定自定义命令在构建过程中的执行时机。理解这三个选项的含义和使用方法,对于我们精确控制构建过程具有重要意义。
PRE_BUILD(预构建)
PRE_BUILD选项表示在所有其他步骤之前执行自定义命令。这个选项非常适合用于执行一些预处理任务,例如清理上一次构建的残留文件,或者检查某些必要的条件是否满足。

例如,我们可以使用PRE_BUILD选项来确保在构建开始之前,输出目录是干净的:

add_custom_command(
    TARGET MyTarget
    PRE_BUILD
    COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/output
    COMMENT "Cleaning up the output directory before build"
)

在这个例子中,我们使用了CMake的remove_directory命令来删除输出目录。这个命令会在每次构建MyTarget目标之前执行。

PRE_LINK(链接前)
PRE_LINK选项表示在链接步骤之前执行自定义命令。这个选项通常用于执行一些需要在编译完成但链接未开始之前的任务,例如生成或更新一些需要链接的库文件。
例如,我们可以使用PRE_LINK选项来生成一个静态库:

add_custom_command(
    TARGET MyTarget
    PRE_LINK
    COMMAND ${CMAKE_AR} rcs libMyLib.a ${MY_LIB_OBJECTS}
    DEPENDS ${MY_LIB_OBJECTS}
    COMMENT "Creating static library libMyLib.a"
)

在这个例子中,我们使用了ar命令来创建一个静态库libMyLib.a。这个命令会在链接MyTarget目标之前执行。

POST_BUILD(构建后)
POST_BUILD选项表示在所有步骤之后执行自定义命令。这个选项通常用于执行一些后处理任务,例如复制生成的文件到指定的目录,或者执行一些测试和验证任务。

例如,我们可以使用POST_BUILD选项来复制生成的可执行文件到一个指定的目录:

add_custom_command(
    TARGET MyTarget
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:MyTarget> ${CMAKE_CURRENT_BINARY_DIR}/bin
    COMMENT "Copying the executable to the bin directory"
)

在这个例子中,我们使用了CMake的

copy命令来复制生成的可执行文件到bin目录。这个命令会在构建MyTarget目标之后执行。

以下表格对比了PRE_BUILD、PRE_LINK和POST_BUILD这三个选项的主要差异:

选项 执行时机 常见用途
PRE_BUILD(预构建) 在所有其他步骤之前 执行预处理任务,如清理上一次构建的残留文件,检查某些必要的条件是否满足
PRE_LINK(链接前) 在链接步骤之前 执行需要在编译完成但链接未开始之前的任务,如生成或更新一些需要链接的库文件
POST_BUILD(构建后) 在所有步骤之后 执行后处理任务,如复制生成的文件到指定的目录,执行一些测试和验证任务
总的来说,PRE_BUILD、PRE_LINK和POST_BUILD这三个选项为我们提供了在不同阶段插入自定义命令的能力,使我们能够更灵活地控制构建过程。在实际使用中,我们应根据实际需求选择合适的选项。

2.2.3 COMMAND选项(COMMAND Option)

在CMake的add_custom_command命令中,COMMAND选项是一个核心的组成部分,它用于指定我们想要执行的命令。这个命令可以是任何有效的命令,包括系统命令、脚本,或者其他的构建工具。下面我们将详细介绍COMMAND选项的使用方法和注意事项。

2.2.3.1 COMMAND选项的基本用法

COMMAND选项后面通常跟随着我们想要执行的命令,例如:

add_custom_command(
    TARGET myTarget
    POST_BUILD
    COMMAND echo "This is a custom command."
)

在这个例子中,我们在myTarget的构建过程中添加了一个自定义命令,这个命令会在所有步骤之后(POST_BUILD)执行,命令的内容是echo “This is a custom command.”,这条命令会在终端打印出一段文字。

2.2.3.2 COMMAND选项的高级用法

COMMAND选项不仅可以执行简单的系统命令,还可以执行复杂的脚本或者其他构建工具。例如,我们可以使用COMMAND选项来执行一个Python脚本:

add_custom_command(
    TARGET myTarget
    POST_BUILD
    COMMAND python3 myScript.py
)

在这个例子中,我们在myTarget的构建过程中添加了一个自定义命令,这个命令会在所有步骤之后(POST_BUILD)执行,命令的内容是python3 myScript.py,这条命令会执行一个Python脚本。

2.2.3.3 COMMAND选项的注意事项

在使用COMMAND选项时,有一些需要注意的地方:

COMMAND选项后面的命令需要是有效的,否则CMake会在构建过程中报错。
如果COMMAND选项后面的命令需要接收参数,那么这些参数需要以空格分隔的形式提供,例如:

add_custom_command(
    TARGET myTarget
    POST_BUILD
    COMMAND echo "This is a custom command." > output.txt
)

在这个例子中,我们在myTarget的构建过程中添加了一个自定义命令,这个命令会在所有步骤之后(POST_BUILD)执行,命令的内容是echo “This is a custom command.” > output.txt,这条命令会在终端打印出一段文字,并将这段文字重定向到output.txt文件中。

如果COMMAND选项后面的命令是一个路径,那么这个路径需要是绝对路径,否则CMake可能无法找到这个命令。
以上就是关于CMake的add_custom_command命令中`

COMMAND选项的详细介绍。在实际使用中,我们可以根据需要灵活地使用COMMAND选项,执行各种不同的命令,从而实现复杂的构建流程。

2.2.3.4 COMMAND选项的实践应用

在实际项目中,COMMAND选项的应用场景非常广泛。例如,我们可以使用COMMAND选项来执行一些预处理或后处理任务,如代码格式化、单元测试、文档生成等。下面是一个使用COMMAND选项进行单元测试的例子:

add_custom_command(
    TARGET myTarget
    POST_BUILD
    COMMAND ctest -R myTest
)

在这个例子中,我们在myTarget的构建过程中添加了一个自定义命令,这个命令会在所有步骤之后(POST_BUILD)执行,命令的内容是ctest -R myTest,这条命令会执行名为myTest的单元测试。

此外,COMMAND选项还可以用于执行一些复杂的构建任务,如调用其他构建工具、执行脚本等。这些高级用法可以帮助我们实现更复杂、更灵活的构建流程。

总的来说,COMMAND选项是add_custom_command命令中非常重要的一个选项,通过灵活地使用COMMAND选项,我们可以在CMake中实现各种复杂的构建任务。

2.2.4 DEPENDS选项详解

DEPENDS选项(DEPENDS Option)在add_custom_command命令中扮演着非常重要的角色。它用于指定自定义命令的依赖关系。在实际的编程过程中,我们经常会遇到一些任务需要在其他任务完成后才能进行,这就是所谓的依赖关系。在CMake中,我们可以通过DEPENDS选项来明确指定这种依赖关系。

DEPENDS选项接受一个或多个文件名作为参数。这些文件名代表了自定义命令的依赖。当这些文件被修改后,自定义命令才会被执行。这种机制保证了我们的构建过程是有效和准确的,只有当依赖的文件发生变化时,相关的构建任务才会被执行,从而避免了不必要的构建任务。

以下是一个使用DEPENDS选项的示例:

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/foo.cpp
  COMMAND generate_foo_cpp
  DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/foo.template
)

在这个示例中,我们定义了一个自定义命令,这个命令的任务是生成一个名为foo.cpp的文件。这个命令依赖于一个名为foo.template的文件,只有当foo.template文件被修改后,这个命令才会被执行。

这就是DEPENDS选项的基本用法和工作原理。在实际的项目中,我们可以根据需要灵活地使用DEPENDS选项,来控制我们的构建过程。

2.2.5 BYPRODUCTS选项详解

在CMake的add_custom_command命令中,BYPRODUCTS选项是一个非常重要的参数。它用于指定自定义命令的副产品。如果你指定了一个或多个文件作为副产品,那么这些文件将会被添加到构建系统的清理列表中。这意味着,当你执行清理操作时,这些被标记为副产品的文件也会被清理掉。

让我们通过一个具体的例子来理解BYPRODUCTS选项的作用。假设我们有一个自定义命令,它的任务是生成一些临时文件,这些临时文件在构建过程中会被使用,但是在构建完成后,这些文件就没有用处了。这时,我们就可以使用BYPRODUCTS选项来指定这些临时文件,这样在执行清理操作时,这些临时文件也会被清理掉。

以下是一个使用BYPRODUCTS选项的例子:

add_custom_command(
    OUTPUT output.txt
    COMMAND ${CMAKE_COMMAND} -E echo "Hello, World!" > output.txt
    BYPRODUCTS temp.txt
)

在这个例子中,我们的自定义命令会生成一个名为output.txt的文件,同时还会生成一个名为temp.txt的临时文件。我们使用BYPRODUCTS选项指定了temp.txt为副产品,这样在执行清理操作时,temp.txt也会被清理掉。

需要注意的是,BYPRODUCTS选项只能在add_custom_command命令中使用,不能在add_custom_target命令中使用。这是因为add_custom_target命令是用来定义一个目标的,而不是用来定义一个命令的。

总的来说,BYPRODUCTS选项是一个非常有用的工具,它可以帮助我们管理构建过程中生成的临时文件,使我们的构建环境保持整洁。

2.2.6 WORKING_DIRECTORY选项

WORKING_DIRECTORY选项是add_custom_command命令中的一个重要参数,它用于指定自定义命令的工作目录。在我们进行项目构建时,有时需要在特定的目录下执行某些命令,这时就可以利用WORKING_DIRECTORY选项来实现。

WORKING_DIRECTORY选项的基本用法
WORKING_DIRECTORY选项的基本用法如下:

add_custom_command(
    TARGET target
    COMMAND command
    WORKING_DIRECTORY dir
)

在这个例子中,dir就是我们指定的工作目录。当CMake执行到这个add_custom_command命令时,它会切换到dir目录下,然后在这个目录下执行command命令。

WORKING_DIRECTORY选项的实际应用
在实际的项目构建中,WORKING_DIRECTORY选项的应用非常广泛。例如,我们可能需要在项目的源代码目录下执行一些预处理命令,或者在构建目录下执行一些后处理命令。这时,我们就可以使用WORKING_DIRECTORY选项来指定这些命令的工作目录。

下面是一个实际的例子:

add_custom_command(
    TARGET my_target
    PRE_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/config.h.in ${CMAKE_BINARY_DIR}/config.h
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

在这个例子中,我们在构建目标my_target之前,先执行了一个命令,这个命令是将config.h.in文件从源代码目录复制到构建目录。并且,我们指定了这个命令的工作目录为构建目录。

这样,无论我们在哪里执行构建命令,这个复制文件的操作都会在正确的目录下执行,保证了构建的正确性。

WORKING_DIRECTORY选项的注意事项
在使用WORKING_DIRECTORY选项时,有一些需要注意的地方:

WORKING_DIRECTORY选项指定的目录必须是存在的,否则CMake会报错。如果你不确定目录是否存在,可以使用file(MAKE_DIRECTORY dir)命令来确保目录的存在。
如果没有指定WORKING_DIRECTORY选项,那么默认的工作目录就是当前的CMakeLists.txt文件所在的目录。
WORKING_DIRECTORY选项只影响当前的add_custom_command命令,不会影响其他的命令或者CMakeLists.txt文件。
以上就是关于WORKING_DIRECTORY选项的详细介绍,希望对你有所帮助。在实际的项目

构建中,我们可以灵活地使用WORKING_DIRECTORY选项,来控制自定义命令的执行环境,从而提高构建的灵活性和可控性。

WORKING_DIRECTORY选项的高级应用
WORKING_DIRECTORY选项不仅可以用于指定命令的工作目录,还可以用于实现一些高级的构建策略。例如,我们可以利用WORKING_DIRECTORY选项来实现跨平台的构建。

在跨平台的构建中,我们可能需要在不同的平台上使用不同的工作目录。例如,我们可能需要在Windows平台上使用一个目录,而在Linux平台上使用另一个目录。这时,我们可以使用CMake的平台检测功能,结合WORKING_DIRECTORY选项,来实现这个需求。

下面是一个例子:

if(WIN32)
    set(dir ${CMAKE_SOURCE_DIR}/win)
else()
    set(dir ${CMAKE_SOURCE_DIR}/linux)
endif()

add_custom_command(
    TARGET my_target
    COMMAND command
    WORKING_DIRECTORY ${dir}
)

在这个例子中,我们首先检测了当前的平台。如果是Windows平台,那么我们就设置工作目录为win目录;否则,我们就设置工作目录为linux目录。然后,我们在add_custom_command命令中使用了这个工作目录。

这样,我们就可以在不同的平台上使用不同的工作目录,从而实现跨平台的构建。

以上就是关于WORKING_DIRECTORY选项的详细介绍,希望对你有所帮助。在实际的项目构建中,我们可以灵活地使用WORKING_DIRECTORY选项,来控制自定义命令的执行环境,从而提高构建的灵活性和可控性。

2.2.7 COMMENT选项

COMMENT选项在add_custom_command命令中起到了非常重要的作用。它的主要功能是为自定义命令提供注释,这些注释将在执行自定义命令时被打印出来。这对于理解和调试构建过程非常有帮助。

COMMENT选项的基本用法
COMMENT选项的基本用法非常简单。你只需要在add_custom_command命令中添加COMMENT选项,然后提供一个字符串作为参数。这个字符串就是你想要打印的注释。例如:

add_custom_command(
    TARGET my_target
    POST_BUILD
    COMMAND my_command
    COMMENT "正在执行 my_command..."
)

在这个例子中,每当my_target被构建,且在所有步骤之后,my_command命令就会被执行。同时,"正在执行 my_command…"这个注释也会被打印出来。

COMMENT选项的高级用法
虽然COMMENT选项的基本用法非常简单,但是你可以通过一些高级技巧来更好地利用它。

首先,你可以使用CMake的变量和表达式来动态生成注释。例如,你可以创建一个变量来存储命令的名称,然后在COMMENT选项中使用这个变量:

set(MY_COMMAND_NAME "my_command")
add_custom_command(
    TARGET my_target
    POST_BUILD
    COMMAND ${MY_COMMAND_NAME}
    COMMENT "正在执行 ${MY_COMMAND_NAME}..."
)

在这个例子中,${MY_COMMAND_NAME}会被替换为my_command,所以打印的注释将是"正在执行 my_command…"。

此外,你还可以使用if命令来根据条件改变注释。例如,你可以根据构建类型(如Debug或Release)来打印不同的注释:

add_custom_command(
    TARGET my_target
    POST_BUILD
    COMMAND my_command
    COMMENT $<$<CONFIG:Debug>:"正在执行 my_command (Debug模式)..."
            $<$<CONFIG:Release>:"正在执行 my_command (Release模式)...">
)

在这个例子中, 和 和 是CMake的生成表达式,它们会根据当前的构建类型来返回true或false。如果当前的构建类型是Debug,那么"正在执行 my_command (Debug模式)…"这个注释将被打印出来。如果当前的构建类型是Release,那么"正在执行 my_command (Release模式)…"这个注释将被打印出来。
COMMENT选项的注意事项
虽然COMMENT选项非常有用,但是在使用它时,还需要注意一些事项。
首先,COMMENT选项的参数必须是一个字符串。如果你提供的参数不是字符串,CMake将会报错。
其次,COMMENT选项的参数不能包含换行符。如果你的注释需要跨多行,你可以使用多个COMMENT选项,每个选项对应一行注释。
最后,虽然COMMENT选项可以帮助你理解和调试构建过程,但是它并不能替代良好的文档和注释。你应该始终确保你的CMake脚本有足够的注释和文档,以便其他人(或者未来的你)能够理解它。

  • 36
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值