摘要:

本文记录一下 CMake 变量的定义、原理及其使用。CMake 变量包含 Normal Variables、Cache Variables。通过 set 指令可以设置两种不同的变量。也可以在 CMake 脚本中使用和设置环境变量。set(ENV{<variable>} <value>...),本文重点讲述 CMake 脚本语言特有的两种变量。

正文:

1、两种变量的定义参考

  • Normal Variables

    通过 set(<variable> <value>... [PARENT_SCOPE])这个命令来设置的变量就是 Normal Variables。例如 set(MY_VAL "666") ,此时 MY_VAL 变量的值就是 666。

  • Cache Variables

    通过 set(<variable> <value>... CACHE <type> <docstring> [FORCE])这个命令来设置的变量就是 Cache Variables。例如 set(MY_CACHE_VAL "666" CACHE STRING INTERNAL),此时 MY_CACHE_VAL 就是一个 CACHE 变量。

2、两种变量的作用域原理及使用

1、Normal Variables

​ 作用域属于整个 CMakeLists.txt 文件,当该文件包含了 add_subdirectory()、include()、macro()、function() 语句时,会出现两种不同的效果。

(1)、包含 add_subdirectory()、function()。(本质是值拷贝)

假设,我们在工程根目录 CMakeLists.txt 文件中使用 add_subdirectory(src) 包含另一个 src 目录,在 src 目录中有另一个 CMakeLists.txt 文件。在终端运行的目录结构如下:

$ tree
.
├── CMakeLists.txt
└── src
└── CMakeLists.txt 1 directory, 2 files

以根目录 CMake 文件为父目录,src 目录为子目录,此时子目录 CMake 文件会拷贝一份父目录文件的 Normal 变量。需要说明的是,我们在子目录中如果想要修改父目录 CMake 文件包含的 Normal 变量。必须通过 set(... PARENT_SCOPE) 的方式。下面通过例子来说明。

在父/根目录的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.10)
message("父目录 CMakeLists.txt 文件")
set(MY_VAL "666") message("第一次在父目录 MY_VAL=${MY_VAL}")
add_subdirectory(src)
message("第二次在父目录,MY_VAL=${MY_VAL}")

在子目录 src/CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.10)
message("进入子目录 src/CMakeLists.txt 文件")
message("在子目录,MY_VAL=${MY_VAL}")
message("退出子目录")

运行结果:

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入子目录 src/CMakeLists.txt 文件
在子目录,MY_VAL=666
退出子目录
第二次在父目录,MY_VAL=666

从结果可以看出,在子目录 CMake 文件中可以直接使用父目录定义的 MY_VAL 变量的值 666。当在子目录 CMake 文件中修改 MY_VAL 变量值,看看在父目录中 MY_VAL 的值如何变化。下面仅仅在子目录 CMake 文件中加入一行代码 set(MY_VAL "777"),最后的子目录 CMake 文件代码如下:

cmake_minimum_required(VERSION 3.10)
message("进入子目录 src/CMakeLists.txt 文件")
set(MY_VAL "777") # 刚刚加入的 message("在子目录,MY_VAL=${MY_VAL}")
message("退出子目录")

运行结果:

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入子目录 src/CMakeLists.txt 文件
在子目录,MY_VAL=777
退出子目录
第二次在父目录,MY_VAL=666

我们发现在 src/CMakeLists.txt 中打印的 MY_VAL 的值是 777,然后退出子目录回到根目录后,打印 MY_VAL 的值仍然是 666。这就说明了:子目录的 CMakeLists.txt 文件仅仅是拷贝了一份父目录的 Normal 变量,即使在子目录 CMake 文件中修改了 MY_VAL 变量,那也只是子目录自己的变量,不是父目录的变量。因为 Normal 变量的作用域就是以 CMakeLists.txt 文件为基本单元。那么我们如何在子目录 CMake 文件中修改父目录 CMake 文件的 Normal 变量呢? 我们需要在子目录 CMakeLists.txt 文件中设置 MY_VAL 时,加上 PARENT_SCOPE 属性。即用如下代码: set(MY_VAL "777" PARENT_SCOPE),子目录 CMakeLists.txt 文件如下:

cmake_minimum_required(VERSION 3.10)
message("进入子目录 src/CMakeLists.txt 文件")
set(MY_VAL "777" PARENT_SCOPE) # 修改处 message("在子目录,MY_VAL=${MY_VAL}")
message("退出子目录")

运行结果:

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入子目录 src/CMakeLists.txt 文件
在子目录,MY_VAL=666
退出子目录
第二次在父目录,MY_VAL=777

可以看出在第二次回到父目录时,MY_VAL 的值已经变成了 777。同理,对于 function() 最开始的结论也适用。代码如下:

cmake_minimum_required(VERSION 3.10)
message("父目录 CMakeLists.txt 文件")
set(MY_VAL "666") message("第一次在父目录 MY_VAL=${MY_VAL}") # 函数定义
function(xyz test_VAL) # 函数定义处!
set(MY_VAL "888" PARENT_SCOPE)
message("functions is MY_VAL=${MY_VAL}")
endfunction(xyz) xyz(${MY_VAL}) # 调用函数
message("第二次在父目录,MY_VAL=${MY_VAL}")

运行结果:

父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
functions is MY_VAL=666
第二次在父目录,MY_VAL=888

可以看出在该函数中使用 MY_VAL 这个变量值,其实就是一份父目录变量的值拷贝,此时打印值为 666。在 函数中修改值,那么也是用 set(${MY_VAL} 888 PARENT_SCOPE)。此时,退出函数第二次打印变量值时。该值就是在函数中修改好的值 888。 本质讲,对于 function() 而言,刚刚说到的父目录其实不是严格正确的。因为函数定义可以是在其他 .cmake 模块文件中定义的。也可以在其他 CMakeLists.txt 文件中调用,因此准确的说,这里的父目录应该改为『调用函数的地方所属的 CMakeLists.txt 』,我们做的这个实验是在根目录 CMakeLists.txt 文件中定义了函数,又在本文件中使用了。因此之前的说法理解其意思即可。对于 add_subdirectory() 而言,其实也是说调用的地方。下面的 include()、macro() 例子会涉及到,将 function() 放在一个外部的 .cmake 文件中。那里也会说明 function() 与 macro() 的不同。

(2)、**包含 include()、macro() **(本质有点类似 c 中的 #include 预处理含义)

现在在上面的根目录中加入了一个 cmake_modules 目录。目录中有一个 Findtest.cmake 文件。新的目录结构如下:

$ tree
.
├── CMakeLists.txt
├── cmake_modules
│   └── Findtest.cmake
└── src
└── CMakeLists.txt 2 directories, 3 files

在根目录中的 CMakeLists.txt 文件包含的代码如下:

cmake_minimum_required(VERSION 3.10)
message("父目录 CMakeLists.txt 文件")
set(MY_VAL "666")
message("第一次在父目录 MY_VAL=${MY_VAL}") # 使用 include() 文件的宏
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules)
include(Findtest) # 从 CMAKE_MODULE_PATH 包含的路径中搜索 Findtest.cmake 文件
#test(${MY_VAL}) # 调用宏
#xyz(${MY_VAL}) # 调用函数 #find_package(test REQUIRED) # 从 CMAKE_MODULE_PATH 包含的路径中搜索 Findtest.cmake 文件 与 include () 两者的效果是一样的! message("第二次在父目录,MY_VAL=${MY_VAL}") message("include test=${test_VAL}")
#message("macro_val=${macro_val}")

cmake_modules/Findtest.cmake 文件内容如下:

# 该文件定义了一个函数以及一个宏
message("进入 Findtest.cmake 文件") set(test_VAL "222") # 验证根目录 CMake 文件能够访问这个变量
set(MY_VAL "000") # 测试 include() 效果 # 宏定义
macro(test MY_VA) # 定义一个宏!
set(macro_val "1") # 宏内部定义变量
message("macro is MY_VAL=${MY_VA}")
set(MY_VAL "999") # 直接修改的就是调用该宏所处的文件中的 Normal 变量
endmacro(test) # 函数定义
function(xyz test_VAL)
set(MY_VAL "888" PARENT_SCOPE) # 修改 调用者的 变量
message("function is MY_VAL=${MY_VAL}")
endfunction(xyz)

运行结果:

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入 Findtest.cmake 文件
第二次在父目录,MY_VAL=000
include test=222

从结果可以看出,include() 内部是可以修改调用者 MY_VAL 变量。include() 包含的文件内定义的变量 test_VAL,也可以在调用 include() 的 CMakeLists.txt 文件中直接访问,同样的对于 macro() 也适用,在根目录 CMake 文件中调用宏,即取消 test(\({MY_VAL}) 以及 message("macro_val=\){macro_val}") 部分的注释,此时最后输出结果 :

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入 Findtest.cmake 文件
macro is MY_VAL=000
第二次在父目录,MY_VAL=999
include test=222
macro_val=1

可以看出,这次输出的结果在第二次进入父目录后,MY_VAL 变量的值就是 999 了。注意到在根目录中 CMakeLists.txt 中 注释语句中有一个 find_package() ,这个和 include() 其实都是一样的结果。

总结:

结合 include() 、macro() 最后结果,能够得出一个结论:通过 include() 和 macro() 相当于把这两部分包含的代码直接加入根目录 CMakeLists.txt 文件中去执行,相当于他们是一个整体。因此变量直接都是互通的。这就有点像 C/C++ 中的 #include 包含头文件的预处理过程了。这一点其实与刚开始讲的 function() 、add_subdirectory() 完全不同,在函数以及 add_subdirectory() 中,他们本身就是一个不同的作用域范围,仅仅通过拷贝调用者的 Normal 值(仅仅在调用 add_subdirectory() / function() 之前的 Normal 变量),如果要修改调用者包含的 Normal 变量,那么只能通过 set(MY_VAL "某个值" PARENT_SCOPE)注明我们修改的是调用者 Normal 值。虽然在 C/C++ 中,可以通过指针的方式,通过函数可以修改外部变量值,但是在 CMake 脚本语言中 function() 虽然能够传入形式参数,但是者本质上就是 C/C++ 中的值拷贝。而不是引用。上面所说的 Normal 变量其实就是一个局部变量。

2、Cache Variables

相当于一个全局变量,我们在同一个 cmake 工程中都可以使用。Cache 变量有以下几点说明:

  • Cache 变量 CMAKE_INSTALL_PREFIX 默认值是 /usr/local (可以在生成的 CMakeCache.txt 文件中查看),这时候如果我们 在某个 CMakeLists.txt 中,仍然使用 set(CMAKE_INSTALL_PREFIX "/usr"),那么此时我们 install 的时候,CMake 以后面的 /usr 作为 CMAKE_INSTALL_PREFIX 的值,这是因为 CMake 规定,有一个与 Cache 变量同名的 Normal 变量出现时,后面使用这个变量的值都是以 Normal 为准,如果没有同名的 Normal 变量,CMake 才会自动使用 Cache 变量。
  • 所有的 Cache 变量都会出现在 CMakeCache.txt 文件中。这个文件是我们键入 cmake .命令后自动出现的文件。打开这个文件发现,CMake 本身会有一些默认的全局 Cache 变量。例如:CMAKE_INSTALL_PREFIX、CMAKE_BUILD_TYPE、CMAKE_CXX_FLAGSS 等等。可以自行查看。当然,我们自己定义的 Cache 变量也会出现在这个文件中。Cache 变量定义格式为 set(<variable> <value> CACHE STRING INTERNAL)。这里的 STRING可以替换为 BOOL FILEPATH PATH ,但是要根据前面 value 类型来确定。参考
  • 修改 Cache 变量。可以通过 set(<variable> <value> CACHE INSTERNAL FORCE),另一种方式是直接在终端中使用 cmake -D var=value .. 来设定默认存在的CMake Cache 变量。

下面通过一个例子来说明以上三点:

首先看一下目录树结构:

$ tree
.
├── CMakeLists.txt
└── src
└── CMakeLists.txt 1 directory, 2 files

根目录 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.10)
set(MY_GLOBAL_VAR "666" CACHE STRING INTERNAL )
message("第一次在父目录 CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
message("第一次在父目录 MY_GLOBAL_VAR=${MY_GLOBAL_VAR}")
add_subdirectory(src)
message("第二次在父目录 CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
message("第二次在父目录 MY_GLOBAL_VAR=${MY_GLOBAL_VAR}")
set(CMAKE_INSTALL_PREFIX "-->usr" )
message("第三次在父目录 CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")

src/CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.10)
message("子目录,CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
message("子目录,MY_GLOBAL_VAR=${MY_GLOBAL_VAR}")
set(CMAKE_INSTALL_PREFIX "/usr" CACHE STRING INTERNAL FORCE)
set(MY_GLOBAL_VAR "777" CACHE STRING INTERNAL FORCE )

运行结果:

$ cmake .
第一次在父目录 CMAKE_INSTALL_PREFIX=/usr/local
第一次在父目录 MY_GLOBAL_VAR=666
子目录,CMAKE_INSTALL_PREFIX=/usr/local
子目录,MY_GLOBAL_VAR=666
第二次在父目录 CMAKE_INSTALL_PREFIX=/usr
第二次在父目录 MY_GLOBAL_VAR=777
第三次在父目录 CMAKE_INSTALL_PREFIX=-->usr

程序说明:首先在根目录中打印一下当前的 Cache 变量 CMAKE_INSTALL_PREFIX 值,主要看看默认值是什么,然后在子目录 src/CMakeLists.txt 中再次打印和修改该 Cache 值,目的是熟悉修改全局 Cache 变量,当返回根目录 CMakeLists.txt 文件中再次执行第二次打印该 Cache 值时,主要看一看在子目录中修改后的效果。接着在根目录中设定一个 CMAKE_INSTALL_PREFIX 的 Normal 同名变量,此时第三次打印 CMAKE_INSTALL_PREFIX 的值,此时是为了证明,当有与 Cache 同名的 Normal 变量出现时,CMake 会优先使用 Normal 属性的值。通过设定 MY_GLOBAL_VAR 主要是为了说明可以自己设定全局 Cache 变量。最后的结果如上面显示,当我们再次执行 cmake . 的时候,程序结果如下:

$ cmake .
第一次在父目录 CMAKE_INSTALL_PREFIX=/usr
第一次在父目录 MY_GLOBAL_VAR=777
子目录,CMAKE_INSTALL_PREFIX=/usr
子目录,MY_GLOBAL_VAR=777
第二次在父目录 CMAKE_INSTALL_PREFIX=/usr
第二次在父目录 MY_GLOBAL_VAR=777
第三次在父目录 CMAKE_INSTALL_PREFIX=-->usr

可以发现第一次在父目录打印 CMAKE_INSTALL_PREFIX 和 MY_GOLBAL_VAR 时,他们的结果是上次cmake .后生成的值,存储在 CMakeCache.txt 中,自己可以找到,解决方案就是可以把 CMakeCache.txt 文件删除,然后在 cmake .我们以后在实际使用时要注意这个坑。对于修改 Cache 变量的另一种方式就是cmake -D CMAKE_INSTALL_PREFIX=/usr。可以自己验证。这里说一个重要的点,就是在终端中输入的 cmake -D var=value . 如果 CMake 中默认有这个 var Cache 变量,那么此时就是赋值,没有的话,CMake 就会默认创建了一个全局 Cache 变量然后赋值。(tips: $CACHE{VAR}表示获取 CACHE 缓存变量的值)。例子如下:(目录结构同上)

根目录 CMakeLists.txt :

cmake_minimum_required(VERSION 3.10)
set(MY_GLOBAL_VAR "666")
message("第一次在父目录 MY_GLOBAL_VAR=$CACHE{MY_GLOBAL_VAR}")
add_subdirectory(src)
message("第二次在父目录局部 MY_GLOBAL_VAR=${MY_GLOBAL_VAR}")
message("第二次在父目录全局 MY_GLOBAL_VAR=$CACHE{MY_GLOBAL_VAR}")

src/CMakeLists.txt :

cmake_minimum_required(VERSION 3.10)
message("子目录,MY_GLOBAL_VAR=${MY_GLOBAL_VAR}")
set(MY_GLOBAL_VAR "777" CACHE STRING INTERNAL FORCE )

运行结果:

第一次在父目录 MY_GLOBAL_VAR=8
子目录,MY_GLOBAL_VAR=666
第二次在父目录局部 MY_GLOBAL_VAR=666
第二次在父目录全局 MY_GLOBAL_VAR=777

有了上面的基础,相信这个例子很快能看明白。

参考:

1、https://stackoverflow.com/questions/31037882/whats-the-cmake-syntax-to-set-and-use-variables/31044116#31044116

2、https://stackoverflow.com/questions/3249459/for-the-cmake-include-command-what-is-the-difference-between-a-file-and-a-mod

3、https://cmake.org/cmake/help/v3.11/command/set.html#set

欢迎大家关注我的微信公众号「小北师兄」,好的文章会优先在里面不定期分享!

比如:

反复研究好几遍,我才发现关于 CMake 变量还可以这样理解!

打开微信客户端,扫描下方二维码即可关注!

CMake 两种变量原理的更多相关文章

  1. 【转载】CMake 两种变量原理

    原文地址:https://cslam.cn/archives/c9f565b5.html 摘要: 本文记录一下 CMake 变量的定义.原理及其使用.CMake 变量包含 Normal Variabl ...

  2. CMake中的两种变量(Variable types in CMake)

    在CMake中存在两种变量:normal variables and cache varialbes .正常变量就像是脚本内部变量,相当于程序设计中定义的局部变量那样.而CMakeLists.txt相 ...

  3. Scala的两种变量

    Scala有两种变量,val和var.val类似于Java的final变量,一旦初始化了,就不能再赋值了.var如同Java中的非final变量,可以在生命周期内被多次赋值.

  4. 再谈java两种变量(基本类型和引用类型)(综合各路大神)

    基本类型: 基本类型自然不用说了,它的值就是一个数字,一个字符或一个布尔值. int  a:   a=250: //声明变量a的同时,系统给a分配了数据空间. 引用类型: 是一个对象类型,值是什么呢? ...

  5. 将应用代码由eclipse导入Android studio的方法NDK-Build和Cmake两种方法(以android_serialport_api为例)

    网上翻了几百篇博客,看了半天,要不就是写的乱七八糟看不懂,要不就是隐藏了一些细节,要不就是实现不了,最后还是在Android官网上看明白了,而且说得有条有理,以后遇到不懂的一定要先翻官网. 参考资料: ...

  6. 批处理命令中set定义的两种变量介绍 计算机基础知识

    摘自: http://www.amhl.net/wenzhang/DianNaoChangShi/20101201/127422.html 所谓的自定义变量,就是由我们来给它赋予值的变量. ①赋值变量 ...

  7. mysql 存储过程中的declare 和 set @的两种变量的区别

    两者在手册中的说明: DECLARE var_name[,...] type [DEFAULT value]这个语句被用来声明局部变量.要给变量提供一个默认值,请包含一个DEFAULT子句.值可以被指 ...

  8. 两个变量交换的四种方法(Java)

    对于两种变量的交换,我发现四种方法,下面我用Java来演示一下. 1.利用第三个变量交换数值,简单的方法. (代码演示一下) class TestEV //创建一个类 { public static ...

  9. 两个变量交换的四种方法(Java) 七种方法(JS)

    两个变量交换的四种方法(Java)   对于两种变量的交换,我发现四种方法,下面我用Java来演示一下. 1.利用第三个变量交换数值,简单的方法. (代码演示一下) 1 class TestEV 2 ...

随机推荐

  1. base64stego 还不懂base64的隐写,详解15行代码带你领略

    网上写了好多关于xctf MISC新手篇的base64Stego隐写的教程,但大都不太清楚,基本上都是讲了一段隐写原理,直接上代码了.但是代码是这道题的关键,代码讲了如何解码这个隐写的完整流程,这次我 ...

  2. liunx磁盘挂载操作

    目标: 欲挂载目录:/home欲挂载磁盘:/dev/sdd 1.查看磁盘使用情况 [root@localhost ~]# df -h 文件系统 容量 已用 可用 已用% 挂载点 devtmpfs 47 ...

  3. 一看就懂的MySQL的聚簇索引,以及聚簇索引是如何长高的

    这一篇笔记我们简述一下 MySQL的B+Tree索引到底是咋回事? 聚簇索引索引到底是如何长高的. 一点一点看,其实蛮好理解的. 如果你看过了我之前的笔记,你肯定知道了MySQL进行CRUD是在内存中 ...

  4. [Linux] Linux命令行与Shell脚本编程大全 Part.3

    Shell Shell 是管理命令行的程序(包在内核外的壳) 不同Shell版本演化关系如下,一般Linux中会自动安装Sh和Bash(Bash比Sh好写,Zsh最好用) .bashrc:Bash 的 ...

  5. 诸神之眼-Nmap 教程 2

    |>>>简版先附上<<<| Nmap基础学习 语法 nmap + [空格] + <选项|多选项|协议> + [空格]+ <目标> 如 nma ...

  6. Docker Swarm(六)Label 节点标签与服务约束

    前言 多节点 Swarm 集群下,可能节点的配置不同(比如 CPU.内存等),部署着不同类型的服务(比如 Web服务.Job服务等),当这些服务以 Service 或者 Stack 的形式部署到集群, ...

  7. dstat命令

    dstat命令 dstat命令是一个用来替换vmstat.iostat.netstat.nfsstat和ifstat这些命令的工具,是一个全能系统信息统计工具.与sysstat相比,dstat拥有一个 ...

  8. 036.Python的TCP语法

    TCP语法 1 建立一个socket对象 import socket sk = socket.socket() print (sk) 执行 [root@node10 python]# python3 ...

  9. 025.Python面向对象以及对对象的操作

    一 面向对象基本概念 1.1 OOP面向对象的程序开发 用几大特征表达一类事物称为一个类,类更像是一张图纸,表达只是一个抽象概念 对象是类的具体实现,更像是由这图纸产出的具体物品,类只有一个,但是对象 ...

  10. ssh登录巨慢加速验证

    ssh登录巨慢怎么办,设计验证是好的,但是那些验证的确没啥用反而造成了一些不便修改/开启 /etc/ssh/ssh_config这三个参数再重启即可取消验证 StrictHostKeyChecking ...