原文地址:https://cslam.cn/archives/c9f565b5.htmlhtml
本文记录一下 CMake 变量的定义、原理及其使用。CMake 变量包含 Normal Variables、Cache Variables。经过 set 指令能够设置两种不一样的变量。也能够在 CMake 脚本中使用和设置环境变量。set(ENV{<variable>} <value>...),本文重点讲述 CMake 脚本语言特有的两种变量。缓存
Normal Variables函数
经过 set(<variable> <value>... [PARENT_SCOPE])这个命令来设置的变量就是 Normal Variables。例如 set(MY_VAL “666”) ,此时 MY_VAL 变量的值就是 666。测试
Cache Variablesui
经过 set(<variable> <value>... CACHE <type> <docstring> [FORCE])这个命令来设置的变量就是 Cache Variables。例如 set(MY_CACHE_VAL "666" CACHE STRING INTERNAL),此时 MY_CACHE_VAL 就是一个 CACHE 变量。spa
做用域属于整个 CMakeLists.txt 文件,当该文件包含了 add_subdirectory()、include()、macro()、function()语句时,会出现两种不一样的效果。指针
假设,咱们在工程根目录 CMakeLists.txt 文件中使用 add_subdirectory(src) 包含另外一个 src 目录,在 src 目录中有另外一个 CMakeLists.txt 文件。在终端运行的目录结构以下:code
$ tree . ├── CMakeLists.txt └── src └── CMakeLists.txt 1 directory, 2 files
以根目录 CMake 文件为父目录,src 目录为子目录,此时子目录 CMake 文件会拷贝一份父目录文件的 Normal 变量。须要说明的是,咱们在子目录中若是想要修改父目录 CMake 文件包含的 Normal 变量。必须经过 set(… PARENT_SCOPE) 的方式。下面经过例子来讲明。orm
在父 / 根目录的 CMakeLists.txt 文件内容以下:htm
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() 的不一样。
如今在上面的根目录中加入了一个 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 变量其实就是一个局部变量。
至关于一个全局变量,咱们在同一个 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
有了上面的基础,相信这个例子很快能看明白。