目录
一.环境变量简单介绍
1.1.示例1——设置与清除
1.2.示例2——全局唯一性和全局可见性
1.3.示例3
1.4.示例4
1.5.示例5
一.环境变量简单介绍
什么是环境变量?
我们看看官网是怎么说环境变量的:cmake-language(7) — CMake 4.1.1 Documentation
环境变量是操作系统提供给所有运行中程序(进程)的一套全局键值对。
CMake 作为一个程序,在启动时会继承它所在 shell(如命令行终端)的所有环境变量。
CMake 自己也提供了一套机制来操作这些变量。
它与 CMake 普通变量的核心区别如下:
1. 作用域 (Scope)
-
全局性: 环境变量在同一个 CMake 进程内部的作用域是全局的。这意味着一旦你设置或修改了一个环境变量,你可以在同一个 CMake 运行过程中的任何地方(无论是在根目录的
CMakeLists.txt
,还是在深层子目录的CMakeLists.txt
,或是在任何.cmake
脚本文件中)访问到它。 -
不被缓存: 环境变量不会被存入 CMake 的缓存(即项目根目录下的
CMakeCache.txt
文件)。缓存变量可以通过set(... CACHE ...)
命令被缓存,从而在多次运行cmake
时保持值不变。环境变量没有这个特性,每次重新运行cmake
命令时,环境变量都会重新从外部系统环境中初始化。
2. 引用方式 (References)
-
你不能像引用普通变量(
${MY_VARIABLE}
)那样直接引用环境变量。 -
你必须使用特殊的
$ENV{<变量名>}
语法。这里的ENV
是一个操作符,它告诉 CMake 你要访问的是环境变量,而不是普通变量。 -
示例: 如果你想获取系统环境变量
PATH
的值,你需要这样写:$ENV{PATH}
。
3. 初始化和修改 (Initialization)
-
初始值: 当你在终端输入
cmake ...
命令时,CMake 进程启动那一刻,它所拥有的环境变量集合就是你的终端 shell 当时的环境变量集合的副本。 -
修改的局限性: 这是最关键的一点。
-
你可以使用
set(ENV{<变量名>} <值>)
命令来设置或修改一个环境变量。 -
你也可以使用
unset(ENV{<变量名>})
命令来删除一个环境变量。 -
但是,这些修改仅仅发生在当前这个正在运行的 CMake 进程内部! 它会产生以下重要影响:
-
不影响系统: 它绝对不会改变你操作系统的环境变量,也不会改变你终端 shell 的环境变量。
-
不回传: 当 CMake 进程结束时,它对环境变量所做的任何修改都会随之消失,不会写回给调用它的父进程(也就是你的终端 shell)。
-
不影响后续进程: 在 CMake 执行过程中,它可能会启动其他的子进程(例如调用
execute_process
命令,或者编译完成后进行构建和测试)。默认情况下,这些子进程启动时会继承最初从父 shell 那里获得的环境变量副本。但是启动子进程之后,如果父进程又修改了环境变量,那么子进程就看不到修改后的环境变量值(除非使用特定命令,见下一条)。
-
-
4. 相关工具 (Tools)
-
cmake -E env
: 正因为直接在 CMake 脚本中修改环境变量对后续构建/测试进程无效,CMake 提供了一个命令行工具来解决这个问题。你可以在调用 CMake 时,使用cmake -E env VAR1=value1 VAR2=value2 <command> ...
来为一个单独的命令临时创建一个修改后的环境,然后在这个新环境中运行该命令。这常用于为build
或test
阶段设置特定的环境。 -
cmake -E environment
: 这个命令可以列出当前 CMake 进程所能看到的所有环境变量及其值。它是一个非常有用的调试工具,可以帮你确认环境变量的值是否如你所预期。
使用环境变量
我们可以去官网看看是怎么说的:set — CMake 4.1.1 Documentation
set(ENV{<变量名>} [<值>])
该命令用于将环境变量设置为指定值。此后在当前进程中通过 $ENV{<变量名>}
获取该环境变量时,将返回新设置的值。
需要注意的是,此命令仅对当前 CMake 进程有效:
-
不会影响调用 CMake 的父进程的环境;
-
不会改变整个系统的环境变量设置;
-
也不会影响后续构建或测试进程的环境。
如果 ENV{<变量名>}
后未提供参数,或 <值>
为空字符串,则该命令将清除该环境变量当前已存在的值。
若在 <值>
后提供了额外参数,这些参数将被忽略。同时,如果检测到有多余参数存在,CMake 会发出作者警告(author warning)。
1.1.示例1——设置与清除
📂 项目目录结构
env_demo/
└── CMakeLists.txt
🔹 env_demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(EnvVarDemo)# 1. 设置环境变量
set(ENV{MY_ENV_VAR} "hello world")
message("设置后: $ENV{MY_ENV_VAR}")# 2. 清除方式一:赋空字符串
set(ENV{MY_ENV_VAR} "")
message("清除方式一后: '$ENV{MY_ENV_VAR}'")# 3. 再次设置
set(ENV{MY_ENV_VAR} "hello again")
message("再次设置后: $ENV{MY_ENV_VAR}")# 4. 清除方式二:不给值
set(ENV{MY_ENV_VAR})
message("清除方式二后: '$ENV{MY_ENV_VAR}'")
其实我们可以通过下面这个命令来一键搭建出这个目录结构和文件
mkdir -p env_demo && \
cat > env_demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(EnvVarDemo)# 1. 设置环境变量
set(ENV{MY_ENV_VAR} "hello world")
message("设置后: $ENV{MY_ENV_VAR}")# 2. 清除方式一:赋空字符串
set(ENV{MY_ENV_VAR} "")
message("清除方式一后: '$ENV{MY_ENV_VAR}'")# 3. 再次设置
set(ENV{MY_ENV_VAR} "hello again")
message("再次设置后: $ENV{MY_ENV_VAR}")# 4. 清除方式二:不给值
set(ENV{MY_ENV_VAR})
message("清除方式二后: '$ENV{MY_ENV_VAR}'")
EOF
接下来我们就来搭建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
两种清理方式都是没有问题的!!
1.2.示例2——全局唯一性和全局可见性
项目结构
demo/
├── CMakeLists.txt # 顶层
├── subdir/
│ ├── CMakeLists.txt # 子目录
│ └── subsubdir/
│ ├── CMakeLists.txt # 子子目录
│ └── helper.cmake # 脚本
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(GlobalEnvDemo)# 在顶层设置环境变量
set(ENV{MY_VAR} "HelloFromTop")
message(STATUS "顶层看到的 MY_VAR: '$ENV{MY_VAR}'")# 进入子目录
add_subdirectory(subdir)# 在所有子目录之后再打印一次,看看是否被修改过
message(STATUS "顶层最后看到的 MY_VAR: '$ENV{MY_VAR}'")
子目录 subdir/CMakeLists.txt
# 子目录也能访问
message(STATUS "子目录看到的 MY_VAR: '$ENV{MY_VAR}'")# 进入子子目录
add_subdirectory(subsubdir)
子子目录 subdir/subsubdir/CMakeLists.txt
# 子子目录访问环境变量
message(STATUS "子子目录看到的 MY_VAR: '$ENV{MY_VAR}'")# 在子子目录里修改环境变量
set(ENV{MY_VAR} "ModifiedInSubSubDir")
message(STATUS "子子目录修改后的 MY_VAR: '$ENV{MY_VAR}'")# 引入 helper 脚本
include(helper.cmake)
脚本 subdir/subsubdir/helper.cmake
# helper.cmake 也能看到修改过的值
message(STATUS "helper.cmake 看到的 MY_VAR: '$ENV{MY_VAR}'")
我们可以运行下面这个bash语句来一键搭建出这个目录结构和文件
mkdir -p demo/subdir/subsubdir && \
cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.18)
project(GlobalEnvDemo)# 在顶层设置环境变量
set(ENV{MY_VAR} "HelloFromTop")
message(STATUS "顶层看到的 MY_VAR: '$ENV{MY_VAR}'")# 进入子目录
add_subdirectory(subdir)# 在所有子目录之后再打印一次,看看是否被修改过
message(STATUS "顶层最后看到的 MY_VAR: '$ENV{MY_VAR}'")
EOFcat > demo/subdir/CMakeLists.txt <<'EOF'
# 子目录也能访问
message(STATUS "子目录看到的 MY_VAR: '$ENV{MY_VAR}'")# 进入子子目录
add_subdirectory(subsubdir)
EOFcat > demo/subdir/subsubdir/CMakeLists.txt <<'EOF'
# 子子目录访问环境变量
message(STATUS "子子目录看到的 MY_VAR: '$ENV{MY_VAR}'")# 在子子目录里修改环境变量
set(ENV{MY_VAR} "ModifiedInSubSubDir")
message(STATUS "子子目录修改后的 MY_VAR: '$ENV{MY_VAR}'")# 引入 helper 脚本
include(helper.cmake)
EOFcat > demo/subdir/subsubdir/helper.cmake <<'EOF'
# helper.cmake 也能看到修改过的值
message(STATUS "helper.cmake 看到的 MY_VAR: '$ENV{MY_VAR}'")
EOF
现在我们就来构建我们的项目
rm -rf build && mkdir build && cd build && cmake ..
✅ 这就展示了:
-
环境变量在同一个 CMake 进程里是 全局可见的。
-
子子目录修改了环境变量之后,顶层
CMakeLists.txt
后续部分也能看到修改后的值。
1.3.示例3
初始值: 当你在终端输入 cmake ...
命令时,CMake 进程启动那一刻,它所拥有的环境变量集合就是你的终端 shell 当时的环境变量集合的副本。
案例1
在你的 终端 里先设置一个环境变量,例如
export MY_VAR=HelloFromShell
写一个简单的 CMakeLists.txt
:
cmake_minimum_required(VERSION 3.18)
project(InitEnvDemo)# 打印 CMake 启动时继承的环境变量
message(STATUS "CMake 看到的 MY_VAR: '$ENV{MY_VAR}'")
运行:
mkdir build && cd build && cmake ..
运行结果
结论
CMake 启动时,确实拿到了 终端 shell 的环境变量副本。
-
如果你在终端里没设置
MY_VAR
,那 CMake 里就是空。 -
如果你在运行
cmake ..
之后再修改 shell 的MY_VAR
,CMake 进程是看不到的(因为它只在启动时复制了一份)。
我们来看看
export MY_VAR=Hello
再次启动看看
这说明 CMake 每次启动时都是从当前 shell 环境复制一份副本。
1.4.示例4
我给你写一个最小例子,演示 CMake 内部修改环境变量不会影响系统或终端 shell。
打开终端,先设置一个环境变量:
export MY_VAR=OriginalValue
写一个简单的 CMakeLists.txt
:
cmake_minimum_required(VERSION 3.18)
project(NoSystemImpactDemo)# 打印启动时 shell 的环境变量
message(STATUS "CMake 启动时看到的 MY_VAR: '$ENV{MY_VAR}'")# 修改 CMake 内部环境变量
set(ENV{MY_VAR} "ModifiedInCMake")
message(STATUS "CMake 内部修改后的 MY_VAR: '$ENV{MY_VAR}'")
运行 CMake:
rm -rf build && mkdir build && cd build && cmake ..
CMake 内部修改了环境变量(set(ENV{MY_VAR} ...)
),只对 CMake 进程和它启动的子进程有效。
系统环境或父 shell 完全不受影响,CMake 退出后修改的值消失。
1.5.示例5
我们都说子进程会继承父进程的环境变量,我们来看看是不是这么一回事。
📂 项目目录结构
env_demo/
└── CMakeLists.txt
🔹 env_demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(MultiChildEnvDemo)# Step 1: CMake 启动时的环境变量
message(STATUS "CMake 启动时看到的 MY_VAR: '$ENV{MY_VAR}'")# Step 2: 修改环境变量
set(ENV{MY_VAR} "FirstValue")
message(STATUS "CMake 修改后的 MY_VAR: '$ENV{MY_VAR}'")# Step 3: 启动第一个子进程
execute_process(COMMAND printenv MY_VAROUTPUT_VARIABLE OUT1OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "第一个子进程看到的 MY_VAR: '${OUT1}'")# Step 4: 再修改环境变量
set(ENV{MY_VAR} "SecondValue")
message(STATUS "CMake 再次修改 MY_VAR: '$ENV{MY_VAR}'")# Step 5: 启动第二个子进程
execute_process(COMMAND printenv MY_VAROUTPUT_VARIABLE OUT2OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "第二个子进程看到的 MY_VAR: '${OUT2}'")
运行 CMake:
rm -rf build && mkdir build && cd build && cmake ..
-
第一次修改 →
"FirstValue"
-
第一个子进程 继承了
"FirstValue"
-
-
第二次修改 →
"SecondValue"
-
第二个子进程 继承了
"SecondValue"
-