【转载】CMake 两种变量原理
原文地址:
摘要:
本文记录一下 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
有了上面的基础,相信这个例子很快能看明白。
参考:
- https://*.com/questions/31037882/whats-the-cmake-syntax-to-set-and-use-variables/31044116#31044116
- https://*.com/questions/3249459/for-the-cmake-include-command-what-is-the-difference-between-a-file-and-a-mod
- https://cmake.org/cmake/help/v3.11/command/set.html#set