目录
- 概述
- 一、示例引入
- 二、语法规则
- 三、变量
- 四、控制结构
- 4.1 条件判断
- 4.2 循环语句
- 4.2.1 foreach循环
- 4.2.2 while循环
- 4.2.3 break、continue
- 五、函数
- 六、文件操作
- 七、环境配置
- 7.1 设置交叉编译
- 7.2 作用域
- 7.3 属性
- 八、补充
- 8.1 数学运算math
概述
首先我们都知道Makefile带来的好处就是自动化编译,但是如果跨平台那样Makefile就都需要重写。而 cmake 就是针对这个问题所诞生,允许开发者编写一种与平台无关的CMakeLists.txt文件来制定整个工程的编译流程,再根据具体的编译平台,生成本地化的Makefile和工程文件,最后执行make编译。
- CMake官网:https://cmake.org/
- Linux开发环境搭建:https://blog.csdn.net/weixin_44567668/article/details/125189852
- Makefile语法:https://blog.csdn.net/weixin_44567668/article/details/137224729
一、示例引入
新建一个main.c文件,内容如下:
#include "stdio.h"
int main()
{
printf("hello\n");
return 0;
}
新建一个CMakeLists.txt文件,内容如下:
project(HELLO)
add_executable(hello ./main.c)
新建一个文件夹build用来存放CMake编译文件,进入文件夹输入cmake ../
命令执行CMake
此时就可以在build文件夹里生成Makefile文件,输入make
运行Makefile
二、语法规则
- 注释
在 CMakeLists.txt 文件中,使用#
号进行单行注释,或者#[[ ]]
块注释
#[[ 这是一个 CMakeLists.txt 文件。
这是一个 CMakeLists.txt 文件
这是一个 CMakeLists.txt 文件]]
cmake_minimum_required(VERSION 3.0.0)
- 基本格式
[command](para1,para2,...)
常用命令:
command | 说明 |
---|---|
add_executable | 可执行程序目标 |
add_library | 库文件目标 |
add_subdirectory | 去指定目录中寻找新的CMakeLists.txt文件 |
aux_source_directory | 收集目录中的文件名并赋值给变量 |
cmake_minimum_required | 设置 cmake 的最低版本号要求 |
get_target_property | 获取目标的属性 |
include_directories | 设置所有目标头文件的搜索路径,相当于 gcc 的-I 选项 |
link_directories | 设置所有目标库文件的搜索路径,相当于 gcc 的-L 选项 |
link_libraries | 设置所有目标需要链接的库 |
list | 列表相关的操作 |
message | 用于打印、输出信息 |
project | 设置工程名字 |
set | 设置变量 |
set_target_properties | 设置目标属性 |
target_include_directories | 设置指定目标头文件的搜索路径 |
target_link_libraries | 设置指定目标库文件的搜索路径 |
target_sources | 设置指定目标所需的源文件 |
具体含义可以查看官方文档:
https://cmake.org/cmake/help/v3.5/manual/cmake-commands.7.html
- 双引号
命令中多个参数之间使用空格进行分隔,而 cmake会将双引号引起来的内容作为一个整体,当它当成一个参数,假如你的参数中有空格(空格是参数的一部分),那么就可以使用双引号,如下所示:
message(Hello World)
message("Hello World")
第一个message命令传入了两个参数,在第一个message命令中,打印信息时,会将两个独立的字符串Hello和World都打印出来,而且 World 会紧跟在Hello 之后,输出如下:
HelloWorld
而第二个 message 命令只传入一个参数,空格为整体,输出如下:
Hello World
- 宏定义
cmake提供了定义宏的方法,cmake中函数function
和宏定义macro
在某种程度上来说是一样的,都是创建一段有名字的代码可以在后面被调用,还可以传参数。通过macro命令定义宏,如下所示:
macro(<name> [arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endmacro(<name>)
测试如下:
# macro 宏
macro(abc arg1 arg2)
if(DEFINED ARGC)
message(true)
else()
message(false)
endif()
endmacro()
# function 函数
function(xyz arg1 arg2)
if(DEFINED ARGC)
message(true)
else()
message(false)
endif()
endfunction()
# 调用宏
abc(A B C D)
# 调用函数
xyz(A B C D)
以上输出说明宏定义只是字符串代替,而不是变量
三、变量
- 变量的使用
在 CMakeLists.txt 文件中可以使用变量,使用set
命令可以对变量进行设置,通过${}
方式来引用变量,如下:
#设置变量 MY_VAL
set(MY_VAL "Hello World!")
#引用变量 MY_VAL
message(${MY_VAL})
可以看出定义变量的格式是变量名+变量值,以下链接可以查到所有内置变量及其介绍:
https://cmake.org/cmake/help/v3.5/manual/cmake-variables.7.html
- 提供信息的变量:
可以用message
命令输出信息
变量 | 说明 |
---|---|
PROJECT_SOURCE_DIR | 工程顶层目录,也就是顶层 CMakeLists.txt 源码所在目录 |
PROJECT_BINARY_DIR | 工 程 BINARY_DIR , 也 就 是 顶 层 CMakeLists.txt 源码的BINARY_DIR |
CMAKE_SOURCE_DIR | 与 PROJECT_SOURCE_DIR 等价 |
CMAKE_BINARY_DIR | 与 PROJECT_BINARY_DIR 等价 |
CMAKE_CURRENT_SOURCE_DIR | 当前源码所在路径 |
CMAKE_CURRENT_BINARY_DIR | 当前源码的 BINARY_DIR |
CMAKE_MAJOR_VERSION | cmake 的主版本号 |
CMAKE_MINOR_VERSION | cmake 的次版本号 |
CMAKE_VERSION | cmake 的版本号(主+次+修订) |
PROJECT_VERSION_MAJOR | 工程的主版本号 |
PROJECT_VERSION_MINOR | 工程的次版本号 |
PROJECT_VERSION | 工程的版本号 |
CMAKE_PROJECT_NAME | 工程的名字 |
PROJECT_NAME | 工程名,与 CMAKE_PROJECT_NAME 等价 |
CMakeLists.txt 文件内容如下:
cmake_minimum_required("VERSION" "3.5")
project(HELLO)
message(${PROJECT_SOURCE_DIR})
message(${PROJECT_BINARY_DIR})
这样进行cmake后就会打印相应信息:
- 描述系统的变量
变量 | 说明 |
---|---|
CMAKE_HOST_SYSTEM_NAME | 运行 cmake 的操作系统的名称(其实就是 uname -s) |
CMAKE_HOST_SYSTEM_PROCESSOR | 运行 cmake 的操作系统的处理器名称(uname -p) |
CMAKE_HOST_SYSTEM | 运行 cmake 的操作系统(复合信息) |
CMAKE_HOST_SYSTEM_VERSION | 运行 cmake 的操作系统的版本号(uname -r) |
CMAKE_HOST_UNIX | 如果运行 cmake 的操作系统是 UNIX 和类 UNIX,则该变量为 true,否则是空值 |
CMAKE_HOST_WIN32 | 如果运行 cmake 的操作系统是 Windows,则该变量为 true,否则是空值 |
CMAKE_SYSTEM_NAME | 目标主机操作系统的名称 |
CMAKE_SYSTEM_PROCESSOR | 目标主机的处理器名称 |
CMAKE_SYSTEM | 目标主机的操作系统(复合信息) |
CMAKE_SYSTEM_VERSION | 目标主机操作系统的版本号 |
ENV | 用于访问环境变量 |
UNIX | 与 CMAKE_HOST_UNIX 等价 |
WIN32 | 与 CMAKE_HOST_WIN32 等价 |
- 改变行为的变量
可以用set
命令进行行为控制
变量 | 说明 |
---|---|
BUILD_SHARED_LIBS | 控制 cmake 是否生成动态库 |
CMAKE_BUILD_TYPE | 指定工程的构建类型,release 或 debug |
CMAKE_SYSROOT | 对应编译器的在–sysroot 选项 |
CMAKE_IGNORE_PATH | 设置被 find_xxx 命令忽略的目录列表 |
CMAKE_INCLUDE_PATH | 为 find_file()和 find_path()命令指定搜索路径的目录列表 |
CMAKE_INCLUDE_DIRECTORIES_BEFORE | 用于控制 include_directories()命令的行为 |
CMAKE_LIBRARY_PATH | 指定 find_library()命令的搜索路径的目录列表 |
CMAKE_MODULE_PATH | 指定要由 include()或 find_package()命令加载的CMake 模块的搜索路径的目录列表 |
CMAKE_PROGRAM_PATH | 指定 find_program()命令的搜索路径的目录列表 |
- 控制编译的变量
变量 | 说明 |
---|---|
EXECUTABLE_OUTPUT_PATH | 可执行程序的输出路径 |
LIBRARY_OUTPUT_PATH | 库文件的输出路径 |
- 使用
list
进行字符串拼接
list(APPEND <list> [<element> ...])
# APPEND表示进行数据追加,后边的参数和set就一样
- 变量作用域
众所周知,Linux的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:
- 根节点CMakeLists.txt中的变量全局有效
- 父节点CMakeLists.txt中的变量可以在子节点中使用
- 子节点CMakeLists.txt中的变量只能在当前节点中使用
四、控制结构
4.1 条件判断
在cmake中可以使用条件判断,条件判断形式如下:
if(expression)
# then section.
command1(args ...)
command2(args ...)
...
elseif(expression2)
# elseif section.
command1(args ...)
command2(args ...)
...
else(expression)
# else section.
command1(args ...)
command2(args ...)
...
endif(expression)
else和endif括号中的expression可写可不写,如果写了,就必须和if中的expression一致。expression就是一个进行判断的表达式,表达式对照表如下:
表达式 | true | false | 说明 |
---|---|---|---|
constant | 如果constant为1、ON、YES、TRUE、Y 或非零数,则为真 | 如果constant为0、OFF 、 NO 、FALSE 、 N 、IGNORE 、NOTFOUND、空字符串或以后缀-NOTFOUND 结尾,则为 False。 | 布尔值大小写不敏感;如果与这些常量都不匹配,则将其视为变量或字符串 |
variable|string | 已经定义并且不是false的变量 | 未定义或者是false的变量 | 变量就是字符串 |
NOT expression | expression为false | expression为true | |
expr1 AND expr2 | expr1 和 expr2 同时为true | expr1 和 expr2 至少有一个为 false | |
expr1 OR expr2 | expr1 和 expr2 至少有一个为 true | expr1 和 expr2 都是 false | |
COMMAND name | name 是一个已经定义的命令、宏或者函数 | name 未定义 | |
TARGET name | name 是add_executable() 、add_library() 或add_custom_target() 定义的目标 | name 未定义 | |
TEST name | name 是由 add_test()命令创建的现有测试名称 | name 未创建 | |
EXISTS path | path 指定的文件或目录存在 | path 指定的文件或目录不存在 | 仅适用于完整路径 |
IS_DIRECTORY path | path 指定的路径为目录 | path 指定的路径不为目录 | 仅适用于完整路径 |
IS_SYMLINK path | path 为符号链接 | path 不是符号链接 | 仅适用于完整路径 |
IS_ABSOLUTE path | path 为绝对路径 | path 不是绝对路径 | |
variable|stringMATCHES regex | variable 与正则表达式regex 匹配成功 | variable 与正则表达式 regex 匹配失败 | |
variable|string IN_LIST variable | 右边列表中包含左边的元素 | 右边列表中不含左边的元素 | |
DEFINED variable | 如果给定的变量已定义,则为真。 | 如果给定的变量未定义 | 只要变量已经被设置,它是真还是假并不重要。(注意宏不是变量。) |
variable|string LESS variable|string | 如果给定的字符串或变量的值是有效数字且小于右侧的数字,则为真。 | 左侧的数字大于或等于右侧的数字 | |
variable|string GREATER variable|string | 如果给定的字符串或变量的值是有效数字且大于右侧的数字,则为真。 | 左侧的数字小于或等于右侧的数字 | |
variable|string EQUAL variable|string | 如果给定的字符串或变量的值是有效数字并且等于右侧的值,则为真 | 左侧的数字不等于右侧的数字 |
上表中只是列出其中一部分表达式 ,还有其它一些表达式见以下链接:
https://cmake.org/cmake/help/v3.5/command/if.html
if(NOT GG)
message(true)
else()
message(false)
endif()
#输出为:true。因为GG没定义
4.2 循环语句
4.2.1 foreach循环
foreach 循环的基本用法如下所示:
foreach(loop_var arg1 arg2 ...)
command1(args ...)
command2(args ...)
...
endforeach(loop_var)
endforeach 括号中的loop_var可写可不写,如果写了,就必须和 foreach 中的loop_var一致。参数 loop_var 是一个循环变量,循环过程中会将参数列表中的变量依次赋值给他,类似于C语言 for 循环中经常使用的变量 i。
RANGE
关键字
foreach(loop_var RANGE stop)
foreach(loop_var RANGE start stop [step])
- 对于第一种方式,循环会从 0 到指定的数字 stop,包含 stop,stop 不能为负数。
- 而对于第二种,循环从指定的数字 start 开始到 stop 结束,步长为 step,不过 step 参数是一个可选参数,如果不指定,默认 step=1;三个参数都不能为负数,而且 stop 不能比 start 小。
foreach(loop_var RANGE 1 4 1)
message("${loop_var}")
endforeach()
IN
关键字
foreach(loop_var IN [LISTS [list1 [...]]] [ITEMS [item1 [...]]])
# foreach 循环测试
set(my_list A B C D)
foreach(loop_var IN LISTS my_list)
message("${loop_var}")
endforeach()
4.2.2 while循环
while(condition)
command1(args ...)
command1(args ...)
...
endwhile(condition)
endwhile括号中的condition可写可不写,如果写了,就必须和while中的 condition 一致。cmake中while循环的含义与C语言中while循环的含义相同,但条件 condition 为真时,执行循环体中的命令,而条件condition的语法形式与if条件判断中的语法形式相同。
4.2.3 break、continue
- break()命令用于跳出循环,和在 C 语言中的作用是一样的
set(loop_var 10)
while(loop_var GREATER 0) #loop_var>0 时 执行循环体
message("${loop_var}")
if(loop_var LESS 6) #当 loop_var 小于 6 时
message("break")
break() #跳出循环
endif()
math(EXPR loop_var "${loop_var} - 1") #loop_var--
endwhile()
- continue()命令用于结束本次循环,执行下一次循环
# 打印所有偶数
set(loop_var 10)
while(loop_var GREATER 0) #loop_var>0 时 执行循环体
math(EXPR var "${loop_var} % 2") #求余
if(var EQUAL 0) #如果 var=0,表示它是偶数
message("${loop_var}") #打印这个偶数
math(EXPR loop_var "${loop_var} - 1")#loop_var--
continue() # 执行下一次循环
endif()
math(EXPR loop_var "${loop_var} - 1")#loop_var--
endwhile()
五、函数
- 定义函数
cmake 提供了function
命令用于定义一个函数,使用方法如下所示:
function(<name> [arg1 [arg2 [arg3 ...]]])
command1(args ...)
command2(args ...)
...
endfunction(<name>)
endfunction括号中的name可写可不写,如果写了,就必须和function括号中的name一致。第一个参数name表示函数的名字,arg1、arg2…表示传递给函数的参数。调用函数的方法其实就跟使用命令一样
- 使用
return
命令
在function函数中也可以使用C语言中的return语句退出函数,如下所示:
# 函数名: xyz
function(xyz)
message(Hello)
return() # 退出函数
message(World)
endfunction()
# 调用函数
xyz()
- 可变参函数
在cmake中,调用函数时实际传入的参数个数不需要等于函数定义的参数个数(甚至函数定义时,参数个数为0),但是实际传入的参数个数必须大于或等于函数定义的参数个数,如下所示:
# 函数名: xyz
function(xyz arg1)
message(${arg1})
endfunction()
# 调用函数
xyz(Hello World China)
- 函数的内部变量
所谓函数的内部变量,指的就是在函数内部使用的内置变量:
函数中的内部变量 | 说明 |
---|---|
ARGVX | X是一个数字,譬如ARGV0、ARGV1、ARGV2、ARGV3…,这些变量表示函数的参数,ARGV0为第一个参数、ARGV1位第二个参数,依次类推! |
ARGV | 实际调用时传入的参数会存放在 ARGV 变量中(如果是多个参数,那它就是一个参数列表) |
ARGN | 假如定义函数时参数为 2 个,实际调用时传入了4个,则ARGN存放了剩下的2个参数(如果是多个参数,那它也是一个参数列表) |
ARGC | 调用函数时,实际传入的参数个数 |
# 函数名: xyz
function(xyz arg1 arg2)
message("ARGC: ${ARGC}")
message("ARGV: ${ARGV}")
message("ARGN: ${ARGN}")
message("ARGV0: ${ARGV0}")
message("ARGV1: ${ARGV1}")
# 循环打印出各个参数
set(i 0)
foreach(loop ${ARGV})
message("arg${i}: " ${loop})
math(EXPR i "${i} + 1")
endforeach()
endfunction()
# 调用函数
xyz(A B C D E F G)
- 函数的作用域
使用 function()定义的函数,我们需要对它的使用范围进行一个简单地了解,譬如有如下工程目录结构:
├── build
├── CMakeLists.txt #顶层
├── hello
├── CMakeLists.txt #子源码
如果我们在子层定义一个函数,在顶层可以调用;反之亦可
六、文件操作
使用file
命令进行文件操作
- 写文件
file(WRITE <filename> <content>...) #给定内容生成文件
file(APPEND <filename> <content>...) #给定内容追加到文件末尾
#由内容生成文件的命令为:
file(GENERATE OUTPUT output-file
<INPUT input-file|CONTENT content>
[CONDITION expression])
- output-file:指定输出文件名,可以带路径(绝对路径或相对路径);
- INPUT input-file:指定输入文件,通过输入文件的内容来生成输出文件;
- CONTENT content:指定内容,直接指定内容来生成输出文件;
- CONDITION expression:如果表达式 expression 条件判断为真,则生成文件、否则不生成文件。
同样,指定文件既可以使用相对路径、也可使用绝对路径,不过在这里,相对路径被解释为相对于当前源码的 BINARY_DIR 路径,而不是当前源码路径。
# 由前面生成的 wtest.txt 中的内容去生成 out1.txt 文件
file(GENERATE OUTPUT out1.txt INPUT "${PROJECT_SOURCE_DIR}/wtest.txt")
# 由指定的内容生成 out2.txt
file(GENERATE OUTPUT out2.txt CONTENT "This is the out2.txt file")
# 由指定的内容生成 out3.txt,加上条件控制,用户可根据实际情况
# 用表达式判断是否需要生成文件,这里只是演示,直接是 1
file(GENERATE OUTPUT out3.txt CONTENT "This is the out3.txt file" CONDITION 1)
- 读文件
# 字节读取
file(READ <filename> <variable> [OFFSET <offset>] [LIMIT <max-in>] [HEX])
从名为filename的文件中读取内容并将其存储在variable中。可选择从给定的offset开始,最多读取max-in字节。HEX选项使数据转换为十六进制表示(对二进制数据有用)
# 以字符串形式读取
file(STRINGS <filename> <variable> [<options>...])
这个命令专用于读取字符串,会将文件中的二进制数据将被忽略,回车符(\r, CR)字符被忽略。
- filename:指定需要读取的文件,可使用绝对路径、也可使用相对路径,相对路径被解释为相对于当前源码路径。
- variable:存放字符串的变量。
- options:可选的参数,可选择 0 个、1 个或多个选项,这些选项包括:
参数 | 作用 |
---|---|
LENGTH_MAXIMUM <max-len> | 读取的字符串的最大长度; |
LENGTH_MINIMUM <min-len> | 读取的字符串的最小长度; |
LIMIT_COUNT <max-num> | 读取的行数; |
LIMIT_INPUT <max-in> | 读取的字节数; |
LIMIT_OUTPUT <max-out> | 存储到变量的限制字节数; |
NEWLINE_CONSUME | 把换行符也考虑进去; |
NO_HEX_CONVERSION | 除非提供此选项,否则 Intel Hex 和 Motorola S-record 文件在读取时会自动转换为二进制文件。 |
REGEX <regex> | 只读取符合正则表达式的行; |
ENCODING <encoding-type> | 指定输入文件的编码格式,目前支持的编码有:UTF-8、UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE。 |
# 从 input.txt 文件读取字符串
file(STRINGS "${PROJECT_SOURCE_DIR}/input.txt" out_var)
message("${out_var}")
# 限定读取字符串的最大长度
file(STRINGS "${PROJECT_SOURCE_DIR}/input.txt" out_var LENGTH_MAXIMUM 5)
message("${out_var}")
# 限定读取字符串的最小长度
file(STRINGS "${PROJECT_SOURCE_DIR}/input.txt" out_var LENGTH_MINIMUM 4)
message("${out_var}")
# 限定读取行数
file(STRINGS "${PROJECT_SOURCE_DIR}/input.txt" out_var LIMIT_COUNT 3)
message("${out_var}")
- 文件重命名
# oldname 指的是原文件,newname 指的是重命名后的新文件
file(RENAME <oldname> <newname>)
- 删除文件.
file(REMOVE [<files>...])
file(REMOVE_RECURSE [<files>...])
REMOVE
选项将删除给定的文件,但不可以删除目录;而REMOVE_RECURSE
选项将删除给定的文件或目录、以及非空目录
- 计算文件的 hash 值
file(<MD5|SHA1|SHA224|SHA256|SHA384|SHA512> <filename> <variable>)
file()命令可以计算指定文件内容的加密散列(hash 值)并将其存储在变量中。MD5|SHA1|SHA224|SHA256|SHA384|SHA512 表示不同的计算 hash 的算法,必须要指定其中之一
七、环境配置
7.1 设置交叉编译
默认情况下,cmake 会使用主机系统(运行 cmake 命令的操作系统)的编译器来编译我们的工程,但如果是编译其他系统的工程就需要用到交叉编译器。
我们使用的交叉编译器如下:
arm-poky-linux-gnueabi-gcc #C 编译器
arm-poky-linux-gnueabi-g++ #C++编译器
配置交叉编译器,文件名为arm-linux-setup.cmake
# 配置 ARM 交叉编译
set(CMAKE_SYSTEM_NAME Linux) #设置目标系统名字
set(CMAKE_SYSTEM_PROCESSOR arm) #设置目标处理器架构
# 指定编译器的 sysroot 路径
set(TOOLCHAIN_DIR /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots)
set(CMAKE_SYSROOT ${TOOLCHAIN_DIR}/cortexa7hf-neon-poky-linux-gnueabi)
# 指定交叉编译器 arm-gcc 和 arm-g++
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/armpoky-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/x86_64-pokysdk-linux/usr/bin/arm-poky-linuxgnueabi/arm-poky-linux-gnueabi-g++)
# 为编译器添加编译选项
set(CMAKE_C_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_CXX_FLAGS "-march=armv7ve -mfpu=neon -mfloat-abi=hard -mcpu=cortex-a7")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
此时 CMakeLists.txt 文件内容如下(剔除了交叉编译的配置项):
# CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_executable(main main.c)
此时工程目录结构如下所示:
├── arm-linux-setup.cmake
├── build
├── CMakeLists.txt
└── main.c
指定配置文件,用以下命令执行cmake:
cmake -DCMAKE_TOOLCHAIN_FILE=cfg_file_path ../arm-linux-setup.cmake
通过**-DCMAKE_TOOLCHAIN_FILE选项指定配置文件,-D**是 cmake 命令提供的一个选项,通过该选项可以创建一个缓存变量(缓存变量就是全局变量,在整个工程中都是生效的,会覆盖 CMakeLists.txt 源码中定义的同名变量 ),所以 -DCMAKE_TOOLCHAIN_FILE其实就是设置了缓存变量CMAKE_TOOLCHAIN_FILE,它的值就是“=”号后面的内容,cmake 会执行 CMAKE_TOOLCHAIN_FILE变量所指定的源文件,对交叉编译进行设置
注意:如果编译报错需要安装最新版本的CMake,地址为:
https://github.com/Kitware/CMake/releases
7.2 作用域
- 函数作用域
当在函数内通过 set 将变量 var 与当前函数作用域绑定时,变量 var仅在函数作用域内有效,出了这个作用域,如果这个作用域外也有同名的变量 var,那么使用的将是域外同名变量 var;func1()内部调用 func2(),嵌套调用的函数 func2()内部如果也引用变量 var,那么该变量 var 应该是 func1()内部定义的变量,如果有的话;如果 func1()内部没有绑定变量 var,那么就会使用 func1()作用域外定义的变量 var,依次向外搜索。
测试代码如下所示:
# 函数 xyz
function(xyz)
message("函数内部")
message("${ABC}")
set(ABC "Hello China!")#设置变量 ABC
message("${ABC}")
endfunction()
set(ABC "Hello World!")#定义变量 ABC
xyz() # 调用函数
message("函数外部")
message("${ABC}")
可以看出函数内修改变量,离开函数后值不变。如果需要在函数内修改外部定义的变量,需在调用 set 命令时在参数列表末尾加上PARENT_SCOPE
关键字
function(xyz)
set(ABC "Hello China!" PARENT_SCOPE) #加上 PARENT_SCOPE
endfunction()
set(ABC "Hello World!")
xyz() # 调用函数
message("${ABC}")
所以可以用PARENT_SCOPE
来实现函数返回值
- 目录作用域(Directory Scope)
子目录会将父目录的所有变量拷贝到当前 CMakeLists.txt 源码中,当前 CMakeLists.txt 中的变量的作用域仅在当前目录有效。目录作用域有两个特点:向下有效(上层作用域中定义的变量在下层作用域中是有效的),值拷贝。譬如目录结构如下所示:
├── CMakeLists.txt
└── sub_dir
└── CMakeLists.txt
父目录 CMakeLists.txt 文件内容如下:
cmake_minimum_required(VERSION 3.5)
project(TEST)
set(parent_var "Hello parent")
message("parent-<parent_var>: ${parent_var}")
add_subdirectory(sub_dir)
message("parent-<parent_var>: ${parent_var}")
子源码 CMakeLists.txt 内容:
message("subdir-<parent_var>: ${parent_var}")
set(parent_var "Hello child")
message("变量修改之后")
message("subdir-<parent_var>: ${parent_var}")
可以看出子源码变量的改变不会影响父源码的parent_var
- 全局作用域(Persistent Cache 持久缓存、缓存变量)
缓存变量在整个 cmake 工程的编译生命周期内都有效,所以这些变量的作用域是全局范围的,工程内的其他任意目录都可以访问缓存变量,注意 cmake 是从上到下来解析 CMakeLists.txt 文件的。
缓存变量可以通过 set 命令来定义,使用 set 命令时添加 CACHE 选项来实现;除此之外,还有其它多种方式可以定义缓存变量,譬如前面给大家介绍的 cmake -D 选项是经常用来定义缓存变量的方法,cmake -DXXX,就表示创建了一个名为 XXX 的全局变量
7.3 属性
属性大概可以分为多种:全局属性、目录属性(源码属性)、目标属性以及其它一些分类。具体见下链接:
https://cmake.org/cmake/help/v3.5/manual/cmake-properties.7.html
- 目录属性
目录属性其实就是CMakeLists.txt源码的属性
属性 | 作用 |
---|---|
CACHE_VARIABLES | 当前目录中可用的缓存变量列表。 |
CLEAN_NO_CUSTOM | 如果设置为true以告诉Makefile Generators在make clean操作期间不要删除此目录的自定义命令的输出文件。 |
INCLUDE_DIRECTORIES | 此属性是目录的头文件搜索路径列表, 其实就是include_directories() 命令所添加的目录 |
LINK_DIRECTORIES | 此属性是目录的库文件搜索路径列表,其实就是 link_directories()命令所添加的目录 |
MACROS | 当期目录中可用的宏命令列表。 |
PARENT_DIRECTORY | 加载当前子目录的源目录 |
VARIABLES | 当前目录中定义的变量列表。只读属性、不可修改! |
- 目标属性
目标属性可通过get_target_property
、set_target_property
命令获取或设置。
属性 | 作用 |
---|---|
BINARY_DIR | 只读属性,定义目标的目录中 CMAKE_CURRENT_BINARY_DIR 变量的值。 |
SOURCE_DIR | 只读属性,定义目标的目录中 CMAKE_CURRENT_SOURCE_DIR 变量的值。 |
INCLUDE_DIRECTORIES | 目标的头文件搜索路径列表,target_include_directories()命令会将目录添加到 INCLUDE_DIRECTORIES列表中,INCLUDE_DIRECTORIES 会拷贝目录属性中的INCLUDE_DIRECTORIES 属性作为初始值。 |
INTERFACE_INCLUDE_DIRECTORIES | target_include_directories()命令使用 PUBLIC 和 INTERFACE 关键字的值填充此属性。 |
INTERFACE_LINK_LIBRARIES | target_link_libraries()命令使用 PUBLIC 和 INTERFACE 关键字的值填充此属性。 |
LIBRARY_OUTPUT_DIRECTORY、LIBRARY_OUTPUT_NAME、LINK_LIBRARIES | 目标的链接依赖库列表。 |
OUTPUT_NAME | 目标文件的输出名称。 |
TYPE | 目标的类型, 它 将 是 STATIC_LIBRARY 、 MODULE_LIBRARY 、 SHARED_LIBRARY 、INTERFACE_LIBRARY、EXECUTABLE 之一或内部目标类型之一。 |
八、补充
8.1 数学运算math
cmake提供了一个命令用于实现数学运算功能,这个命令就是math
,如下所示:
math(EXPR <output variable> <math expression>)
math 命令中,第一个参数是一个固定的关键字EXPR;第二个参数是一个返回参数,将数学运算结果存放在这个变量中;而第三个参数则是一个数学运算表达式,支持的运算符包括:+(加)、-(减)、*(乘)、/(除)、%(求余)、|(按位或)、&(按位与)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)以及这些运算符的组合运算,它们的含义与 C 语言中相同。
math(EXPR out_var "100 + 100") # 计算100 + 100
message("${out_var}")