欢迎光临
我们一直在努力

Cmake (2)

1.变量

1.作用域

在 CMake 中,变量主要有 三层作用域:

函数作用域 → 目录作用域 → CACHE(全局缓存)

  • Block Scope (块作用域):

    • block() 命令可以创建一个新的作用域用于变量绑定。当你在 block() 内部使用 set() 或 unset() 命令时,变量会在该作用域内被定义,并且这些变量只能在当前作用域及其嵌套的调用中有效,但在函数返回之后不可见。

  • Function Scope (函数作用域):

    • function() 命令创建命令,这些命令在调用时会处理和记录它们在新创建的变量绑定作用域中的行为。在函数内部使用 set() 或 unset() 时,变量只在当前函数和它的嵌套调用中有效。也就是说,函数外部无法访问这些变量。

  • Directory Scope (目录作用域):

    • CMake中的每个目录都可以有自己的变量绑定。在处理CMakeLists.txt文件时,CMake会复制父目录中已定义的所有变量绑定到当前目录作用域中。如果需要在当前目录作用域中定义新的变量,可以使用 set() 或 unset()。

  • Persistent Cache (持久缓存):

    • CMake会存储一个单独的“缓存”变量,缓存变量会跨多个构建树保留其值。这些缓存条目会被修改,只有在运行 set() 或 unset() 命令时,缓存值才会发生变化

含义:

作用域说明
函数作用域 只在 function() 内部有效
目录作用域 当前 CMakeLists.txt 文件及子目录
CACHE 全局缓存,所有 CMake 运行都能访问

1. 变量引用:

  • 在CMake中,变量的引用使用 ${variable} 这种格式。

  • 如果变量未被定义,CMake会根据作用域的不同,在执行时查找指定的缓存项的值。

2. 引用带参数或不带参数的情况:

  • 带引号参数的引用:如果变量是一个带参数的引用,CMake会尝试替换其中的内容。如果两个参数未设置,则会替换为空字符串。

  • 不带引号参数的引用:引用的内容可以通过不同方式被替换和处理。

3. 缓存值:

  • 如果一个变量被指定为缓存值(通过 set() 命令指定),则该变量的值会保存在缓存中,即使在不同的CMake执行过程中,也可以被调用。

用变量:在CMake中引用变量时,要使用 ${} 来包围变量名。例如,${MY_VAR}。

• 处理未定义变量:如果你引用了一个未定义的变量,CMake会根据当前作用域和缓存设置,尝试获取该变量的值。如果缓存中没有该值,它会变为空字符串或触发错误。

• 缓存:通过 set() 命令,可以将变量存储在CMake缓存中,以便在多个构建过程中共享并持久化这些变量。

什么是 CACHE 变量

CACHE 是 CMake 的全局持久变量。

特点:

  • 保存在 CMakeCache.txt

  • 重新运行 CMake 仍然存在

  • 常用于 配置选项

示例:

set(MY_OPTION ON CACHE BOOL "enable feature")

意思:

创建一个 缓存变量 MY_OPTION

参数解释:

set(变量 值 CACHE 类型 描述)

类型常见:

类型说明
BOOL 开关
STRING 字符串
PATH 路径
FILEPATH 文件路径

何读取 CACHE 变量

$CACHE{name}

意思:

直接读取 CACHE 中的变量

特点:

  • 不会查普通变量

  • 只查 CACHE

示例:

message("$CACHE{MY_OPTION}")

 1.函数可以读取父目录变量

 2.函数内部变量是临时的

3.函数结束后变量 不会保存到外层

4.尽量不要用 PARENT_SCOPE
除非你非常理解作用域机制。

2.函数

# 定义函数:函数名后直接跟参数名(形参)
function(自定义函数名 [参数1] [参数2] …)
# 函数体:通过 ${参数名} 或 ${ARGVx} 访问参数
# 核心内置变量(必记):
# ${ARGC}:参数总数
# ${ARGV}:所有参数的列表(整体)
# ${ARGV0}:第1个参数,${ARGV1}:第2个参数,以此类推
# ${ARGN}:所有未命名的额外参数(可变参数)
endfunction()

# 调用函数:函数名后跟实际参数(实参)
自定义函数名(实参1 实参2 …)

函数的具体作用在后面例子中能体现出来

3.函数作用域

#变量值查找的作用域顺序
#块作用域->函数作用域->上级目录作用域->全局缓存 顺序查找
不存在该作用域情况下直接跳过

cmake_minimum_required(VERSION 3.18)

project(FunctionScopDemo)

set(SCOP "Directory Scop")

function(print_var)
# 1 第一次打印 此时按照
#函数作用域->上级目录作用域->全局缓存
#在上级目录作用域找到 为Directory Scop
message("${SCOP}")

#在函数内创建一个SCOP变量
#和函数外的SCOP变量是两个变量
set(SCOP "Function Directory")
#函数作用域->上级目录作用域->全局缓存 顺序查找
#在函数作用域找到 Function Directory
message("${SCOP}")
endfunction(print_var)

#调用自己定义的函数
print_var()

#此时出了函数作用域
#查找顺序 上级目录作用域->全局缓存
#在上级目录作用域找到 Directory Scop
message("${SCOP}")

4.块作用域

# 基础结构:block + 可选参数 + 块体 + endblock
block([OPTIONS])
# 块内代码:变量/策略仅在块内生效(除非显式指定 PARENT_SCOPE)
# 支持的 OPTIONS:
# PARENT_SCOPE:块内变量修改会同步到父级(慎用)
# POLICY_SCOPE:块内的策略设置仅在块内生效
endblock()

块作用域和函数作用域类似 只不过块作用域是顺序执行

而函数作用域确需要调用执行

5.上级目录作用域

#./CMakeLists.txt文件代码
cmake_minimum_required(VERSION 3.18)

project(FunctionScopDemo)

set(SCOP "Main SCOP")

message("main1:${SCOP}")

add_subdirectory(subdir)

message("main2:${SCOP}")

# ./subdir/CMakeLists.txt的代码
message("sub1:${SCOP}")
set(SCOP "sub SCOP")
message("sub2:${SCOP}")

总结

:读可见 写隔离

第一:子目录会拷贝父目录全部的普通变量

第二:子目录修改变量只会影响子目录自己,对父目录不可见。

第三:如果要设置父目录的变量,可以使用 PARENT_SCOPE 选项。只是修改了父目录的值,子目录的值并没有变。

有点类似linux操作系统的写时拷贝机制

PARENT_SCOPE

我们把# ./subdir/CMakeLists.txt的代码 进行一点点修改 其他不变

#message的时候会发生写实拷贝 在子目录创建一个变量SCOP
message("sub1:${SCOP}")
#此时修改的是父目录 不会修改子目录
set(SCOP "sub SCOP " PARENT_SCOPE)
message("sub2:${SCOP}")

  • 仅向上传递一层:如果是「主目录→子目录→孙子目录」,孙子目录用 PARENT_SCOPE 只能修改子目录的变量,无法直接修改主目录(需子目录再传递一次)。
  • 子作用域本身不生效:加 PARENT_SCOPE 后,当前子作用域的变量不会被修改,只有父作用域会变(比如上面函数内打印还是初始值)。

当你在子作用域(函数 / 子目录 /block)中使用 set(变量名 "值" PARENT_SCOPE) 时,如果父作用域原本没有这个变量,CMake 会在父作用域中「新建」这个变量;
如果父作用域已有该变量,则会覆盖原有值。

1. 第一步:message("sub1:${SCOP}")
子目录原本没有 SCOP,执行 ${SCOP} 读取时,CMake 会触发「写时拷贝(Copy-on-Write)」:
先从父目录「拷贝」SCOP=Main SCOP 到子目录,创建子目录的局部 SCOP 变量;
然后 message 打印这个局部变量 → sub1:Main SCOP。
2. 第二步:set(SCOP "sub SCOP " PARENT_SCOPE)
这个操作仅对父目录生效:直接把父目录的 SCOP 改成 sub SCOP ;
子目录的局部 SCOP 不受影响(依然是 Main SCOP),因为 PARENT_SCOPE 不会修改当前作用域变量。
3. 第三步:message("sub2:${SCOP}")
此时读取的是子目录自己的局部 SCOP(第一步拷贝的 Main SCOP),而非父目录的新值;
所以输出 sub2:Main SCOP,而非 sub SCOP 。

那么如果是下面这样呢

set(SCOP "sub SCOP " PARENT_SCOPE)
message("sub2:${SCOP}")

父目录:set(SCOP "Main SCOP") → main1:Main SCOP
进入子目录:
子目录里没有自己的 SCOP 变量,所以读取时会继承父目录的值 → sub1:Main SCOP
执行 set(SCOP "sub SCOP" PARENT_SCOPE):直接修改父目录的 SCOP 为 sub SCOP,但子目录本身不会创建 SCOP 变量,所以子目录里的 ${SCOP} 还是读父目录修改前的旧值 Main SCOP
回到父目录:父目录的 SCOP 已经被更新 → main2:sub SCOP

6.缓存变量

set(<变量名> <默认值> CACHE <类型> "<描述文本>")

我们先来看一段代码的执行 结果 和我们平时执行结果是一样的

我们发现刚刚的缓存变量被存放到构建目录的CMakeCache.txt里面了

但是我们接下来会发现 当我把代码改了之后再执行

还是Tony

但是如果我们带上了FORCE就不一样了 就会把原来存在CMakeCache.txt的Tony覆盖成Herry了

对同一个缓存变量:
第一次设置时,会直接写入缓存;
第二次(及以后)设置时:
不带 FORCE 选项 → 不会覆盖 缓存里已有的值(第一次的值保留);
带 FORCE 选项 → 强制覆盖 缓存里已有的值(第二次的值生效)。

还有一种方法也可以实现类似于FORCE的效果

CMake 提供了 -D 参数(-D<变量名>=<值>),可以在执行 cmake 命令时直接设置 / 覆盖缓存变量,效果和代码里加 FORCE 完全一致,且优先级更高。

同样Smith也会覆盖原来的Heryy

那么同时用FORCE 和-D哪个优先级更高呢?

答案是-D

存变量(CMakeCache.txt 中的条目)解决的是 “信息在多次 cmake 调用之间、以及在整个源码树各目录之间如何共享并持久化” 的问题。

缓存变量是 CMake 提供的 “全局、持久、可由用户交互修改” 的配置键值存储,用来保存探测结果、构建选项以及工具链信息,从而让同一构建树的后续 reconfigure 变得快速、可重复且可控。
不受作用域限制。

7.环境变量

环境变量是操作系统级别的变量,CMake 通过 $ENV{VAR} 语法访问。

特点
  • 作用域:全局可见,但需要通过 $ENV{VAR} 显式引用。
  • 生命周期:取决于操作系统,与 CMake 运行会话无关。
  • 用途:用于获取系统配置(如 PATH、HOME)或传递外部配置参数。

核心结论:进程设置的环境变量是存储在进程内存中的。


两种环境变量来源
  • 第一种:shell 设置的
    • 与操作系统保持一致
  • 第二种:在进程里设置的环境变量
    • 与进程保持一致(如 C/Go/CMake 中设置)
    • 存储在进程内存
  • 我么很明显能发现进程内的环境变量 生命周期和进程保持一致

    CMake 三种变量特性对比表

    特性普通变量缓存变量环境变量
    定义语法 set(VAR value) set(VAR value CACHE …) 系统层面设置,CMake 读取
    作用域 目录 / 函数 全局 全局
    存储位置 内存(CMake 进程) CMakeCache.txt 操作系统环境
    优先级 普通变量最高 缓存变量次之 需要显式引用
    修改方式 重新运行 CMake -D 命令行参数或手动编辑 修改系统环境或临时设置

    赞(0)
    未经允许不得转载:171主机测评 » Cmake (2)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址