摘要:

本文记录一下 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. java设计模式之单例模式你真的会了吗?(懒汉式篇)

    java设计模式之单例模式你真的会了吗?(懒汉式篇) 一.什么是单例模式? 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供 ...

  2. 【近取 key】功能规格说明书

    目录 前置信息说明 概念介绍 记忆宫殿 A4纸背单词法 词图 单词掌握程度相关 用户和典型场景 系统功能设计 主页 词图相关功能 创建词图 查看词图 复习词图 个人控制台相关功能 我的词图 统计信息 ...

  3. MSSQL·备份数据库中的单表

    阅文时长 | 0.11分钟 字数统计 | 237.6字符 主要内容 | 1.引言&背景 2.声明与参考资料 『MSSQL·备份数据库中的单表』 编写人 | SCscHero 编写时间 | 20 ...

  4. docker 日志位置

    日志分两类,一类是 Docker 引擎日志:另一类是 容器日志. Docker 引擎日志 Docker 引擎日志 一般是交给了 Upstart(Ubuntu 14.04) 或者 systemd (Ce ...

  5. Docker Swarm(一)集群部署

    一.机器环境 机器规划 172.16.0.89 swarm的manager节点 manager-node 172.16.0.90 swarm的node节点 node1 机器版本(均是:CentOS L ...

  6. sizeof()用法汇总-(转自风雷)

    sizeof()功能:计算数据空间的字节数 1.与strlen()比较       strlen()计算字符数组的字符数,以"\0"为结束判断,不计算为'\0'的数组元素.     ...

  7. kylin优化的思想

    kylin的核心优势在于使用额外的空间存储预计算的结果,以换取查询时间的缩减. 所以我们要对额外的空间进行优化,并且在空间优化之后,cuboid无法完全命中时,对查询时间进行优化. 空间优化的思路就是 ...

  8. JAVA基础语法-day01

    JAVA基础语法 1.注释 单行注释--// 多行注释--/* */ 文档注释--/** */ 2.标识符 只能大小写字母,$,下划线开头,其它不行. 3.数据类型 基本数据类型--整数,浮点,字符, ...

  9. 根据swagger.json生成flutter model,暂无空安全支持

    一般的服务端类型都有泛型支持,对于flutter来说虽然也支持泛型,但是在序列化这里却始终存在问题,flutter不允许用反射,对于flutter项目的开发来说除了画页面,可能最烦人的就是跟服务端打交 ...

  10. 郑政 | 2021软件代码开发技术作业四 | 需求改进&系统设计

    需求改进&系统设计 -------------------------------------------------------------------------------------- ...