CMake指令:set()

目录

1.简介

2.变量类型与核心参数

3.关键参数详解

4.变量作用域与生命周期

5.常见应用场景

6.注意事项

7.总结


1.简介

在 CMake 中,set 命令是设置变量(或属性)的核心工具,用于管理项目配置、编译选项、路径信息等。它支持多种变量类型(普通变量、缓存变量、环境变量等),并能控制变量的作用域和可见性。

基本语法:

cpp 复制代码
set(<variable> <value>... [PARENT_SCOPE]) #设置普通变量
set(<variable> <value>... CACHE <type> <docstring> [FORCE]) #设置缓存条目
set(ENV{<variable>} [<value>]) #设置环境变量

其中 <variable> 是变量名,<value> 是变量值(支持多个值,用空格分隔),[OPTIONS] 是可选参数,控制变量的类型和行为。

set的值<value>...表示可以给变量设置0个或者多个值,当设置多个值时(大于2个),多个值会通过分号连接符连接成一个真实的值赋值给变量,当设置0个值时,实际上是把变量变为未设置状态,相当于调用unset命令。

如果使用了PARENT_SCOPE选项,意味着该变量的作用域会传递到上一层(也就是上一层目录或者当前函数的调用者 ,如果是函数则传递到函数的调用者,如果是目录则传递到上一层目录),并且在当前作用域 该变量不受带PARENT_SCOPE选项的set命令的影响(如果变量之前没有定义,那么在当前作用域仍然是无定义的;如果之前有定义值,那么值和之前定义的值保持一致)。

2.变量类型与核心参数

set 命令支持以下变量类型,通过不同的参数区分:

1. 普通变量(最常用)

存储项目内部使用的临时值(如源文件列表、路径等),作用域为当前目录及子目录 (通过 add_subdirectory 引入的子目录可继承)。

cpp 复制代码
set(<variable> <value1> <value2> ...)  # 多个值会被合并为列表(用分号分隔)

关于变量的作用域:每一个新的目录或者函数都会创建一个新的作用域,普通变量的作用域,如果不使用PARENT_SCOPE选项,只能从外层往内层传递

示例如下:

cpp 复制代码
# 设置源文件列表
set(SOURCES main.cpp utils.cpp)  //输出: main.cpp;utils.cpp

# 设置头文件路径
set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include")

2.缓存变量(CACHE 变量)

存储需要用户自定义的配置选项(如编译器路径、功能开关),会被写入 CMakeCache.txt 文件,支持通过 cmake-gui 或命令行修改。

cpp 复制代码
set(<variable> <value> CACHE <type> <docstring> [FORCE])

将缓存条目variable设置为值<value> ...,除非用户进行设置或使用了选项FORCE,默认情况下缓存条目的值不会被覆盖。缓存条目可以通过CMAKE的GUI界面的add entry按钮来增加。缓存条目的实质为可以跨层级进行传递的变量,类似于全局变量。

  • 参数说明

    • <type>:变量类型(影响 cmake-gui 中的输入方式),可选值:
      BOOL(布尔值)、FILEPATH(文件路径)、PATH(目录路径)、STRING(字符串)、INTEGER(整数),INTERNAL(文本行)。
    • <docstring>:变量描述(在 cmake-gui 中显示)。
    • FORCE:强制覆盖已存在的缓存变量(即使已被用户修改过)。

type各类型详细说明:

  • BOOL:布尔值ON/OFF,CMAKE的GUI界面对此类缓存条目会提供一个复选框。
  • FILEPATH:文件路径,CMAKE的GUI界面对此类缓存条目会提供一个文件选择框。
  • PATH:目录路径,CMAKE的GUI界面对此类缓存条目会提供一个目录选择框。
  • STRING / STRINGS:文本行,CMAKE的GUI界面对此类缓存条目会提供一个文本框(对应STRING)或下拉选择框(对应STRINGS)。
  • INTERNAL:文本行,但是只用于内部,不对外呈现。主要用于运行过程中存储变量,因此使用该type意味着使用FORCE。

缓存条目的几个注意事项:

  • 如果变量先前未定义或者使用了FORCE选项,则缓存条目会直接被赋值。
  • 可以在使用cmake构建的使用通过-D选项来给缓存条目赋值,这样CMakeLists.txt内的set命令只会为缓存条目添加类型。
  • 如果变量类型是目录或者文件路径,通过-D选项传入的若只是相对路径,那么set会给这个相对路径前添加当前的工作目录以变成绝对路径(如果已经是绝对路径则不会处理)。

示例如下:

cpp 复制代码
cmake_minimum_required (VERSION 3.10.2)
project (set_test)

set (cache_entry_val ON OFF CACHE BOOL "choose ON to enable")
message (">>> value = ${cache_entry_val}")

set (cache_entry_val2 ON CACHE BOOL "choose ON to enable" FORCE)
message (">>> value2 = ${cache_entry_val2}")

set (cache_entry_val3 ON)
set (cache_entry_val3 OFF CACHE BOOL "choose ON to enable")
message (">>> value3 = ${cache_entry_val3}")

set (cache_entry_input OFF CACHE BOOL "choose ON to enable")
message (">>> value4 = ${cache_entry_input}")

set (mypath "test" CACHE FILEPATH "choose a file path")
message (">>> value5 = ${mypath}")
cpp 复制代码
# 输入cmake构建,使用-D选项
cmake . -Dcache_entry_input=ON -Dmypath=sub

# 输出
>>> value = ON;OFF
>>> value2 = ON
>>> value3 = ON
>>> value4 = ON
>>> value5 = /XXX/XXX/XXX/sub

3.环境变量(ENV 变量)

设置构建过程中使用的环境变量(如编译器标志、库路径),通过 ENV{<var>} 访问。

cpp 复制代码
set(ENV{<variable>} <value>)  # 设置环境变量(覆盖原有值)
set(ENV{<variable>} "$ENV{<variable>};${value}")  # 追加值到环境变量

示例如下:

cpp 复制代码
# 设置编译器标志(覆盖原有值)
set(ENV{CXXFLAGS} "-O2 -Wall")

# 追加库路径到 LD_LIBRARY_PATH
set(ENV{LD_LIBRARY_PATH} "/usr/local/lib:$ENV{LD_LIBRARY_PATH}")

4.属性变量(Target/Directory/Global 属性)

设置目标(Target)、目录(Directory)或全局(Global)的属性(如编译选项、链接标志)。

cpp 复制代码
set_property(<scope> <target> PROPERTY <prop> <value>)  # 更推荐使用 set_property
# 等效于(但 `set` 仅支持部分属性):
set(<target>_<prop> <value>)

示例如下:(不推荐直接用 set,优先用 set_property

cpp 复制代码
# 设置目标的 C++ 标准(等效于 target_compile_features)
set_property(TARGET my_target PROPERTY CXX_STANDARD 17)

3.关键参数详解

1.PARENT_SCOPE

将变量设置到父作用域(当前目录的上一级目录)。子目录的 CMakeLists.txt 需将变量传递给父目录。示例如下:

cpp 复制代码
# 子目录(subdir/CMakeLists.txt)中设置变量
set(SUBDIR_SOURCE "sub.cpp" PARENT_SCOPE)

# 父目录可获取该变量
message("Subdir source: ${SUBDIR_SOURCE}")  # 输出 "sub.cpp"

注意:在函数内使用选项PARENT_SCOPE,对应的作用域只能传递到调用它的函数。

场景1 :在函数内使用选项PARENT_SCOPE定义变量,在函数定义的文件中(非另一个函数中)使用该变量。
结果 :变量无定义。
结论:函数内定义的变量,在函数定义的文件中调用,找不到变量的定义。

cpp 复制代码
cmake_minimum_required (VERSION 3.10.2)
project (set_test)

function (test_fn arg1)
    set (normal_var_in_fn ${arg1} PARENT_SCOPE)
endfunction (test_fn)
message (">>> in directory, value = ${normal_var_fn}")

输出为:

cpp 复制代码
>>> in directory, value =
>>> in function, value =

场景2 :在函数内使用选项PARENT_SCOPE定义变量,在函数内使用该变量。
结果 :变量无定义。
结论:函数内使用选项PARENT_SCOPE定义的变量,在函数内也是无定义的。

cpp 复制代码
cmake_minimum_required (VERSION 3.10.2)
project (set_test)

 function (test_fn arg1)
    set (normal_var_in_fn ${arg1} PARENT_SCOPE)
    message (">>> in function, value = ${normal_var_fn}")
endfunction (test_fn)

test_fn (hello)

输出为:

cpp 复制代码
>>> in function, value =

场景3:在函数内使用选项PARENT_SCOPE定义变量,在函数内使用该变量,并且使用set命令不带PARENT_SCOPE选项定义过该变量。

结果:函数内的变量值为不带PARENT_SCOPE选项的set命令所定义的。

结论:选项PARENT_SCOPE定义的变量作用域在上一层函数,当前函数的变量必须使用不带选项PARENT_SCOPE定义。

cpp 复制代码
cmake_minimum_required (VERSION 3.10.2)
project (set_test)

function (test_fn arg1)
    set (normal_var_in_fn nohello)
    set (normal_var_in_fn ${arg1} PARENT_SCOPE)
    message (">>> in function, value = ${normal_var_in_fn}")
endfunction (test_fn)

test_fn (hello)

输出为:

cpp 复制代码
>>> in function, value = nohello

场景4 :在函数(示例中为test_fn)内使用选项PARENT_SCOPE定义变量,在另一个函数(调用者,示例中为test_fn_parent)内调用该函数。
结果:调用者函数内有该变量的定义。

结论:选项PARENT_SCOPE将变量传递到上一层调用函数。

cpp 复制代码
cmake_minimum_required (VERSION 3.10.2)
project (set_test)

function (test_fn arg1)
    set (normal_var_in_fn nohello)
    set (normal_var_in_fn ${arg1} PARENT_SCOPE)
    message (">>> in function, value = ${normal_var_in_fn}")
endfunction (test_fn)

function (test_fn_parent arg1)
    test_fn (${arg1})
    message (">>> in parent function, value = ${normal_var_in_fn}")
endfunction (test_fn_parent)

test_fn_parent (hello)

输出为:

cpp 复制代码
>>> in function, value = nohello
>>> in parent function, value = hello

2.STRINGS

为缓存变量(CACHE 类型)提供候选值(仅对 STRING 类型有效)。示例如下:

cpp 复制代码
set(BUILD_TYPE "Release" CACHE STRING "Build type (Debug/Release)")
set_property(CACHE BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
  • cmake-gui 中,BUILD_TYPE 会显示为下拉列表,选项为指定的候选值。

4.变量作用域与生命周期

变量类型 作用域 生命周期
普通变量 当前目录及子目录(通过 add_subdirectory 继承) 仅在 CMake 配置阶段有效,不写入缓存
缓存变量(CACHE) 全局(所有目录可见) 写入 CMakeCache.txt,跨配置阶段保留
环境变量(ENV) 构建过程(如 makeninja 阶段) 仅在当前 CMake 进程中有效

5.常见应用场景

1. 管理源文件与路径

cpp 复制代码
# 设置源文件列表(普通变量)
set(SOURCES
    main.cpp
    utils/string_utils.cpp
    utils/file_utils.cpp
)

# 设置头文件路径(普通变量)
set(INCLUDE_DIRS
    "${PROJECT_SOURCE_DIR}/include"
    "${PROJECT_BINARY_DIR}/generated"
)

# 添加到目标(结合 target_include_directories)
target_include_directories(my_target PUBLIC ${INCLUDE_DIRS})

2.控制编译选项(缓存变量)

cpp 复制代码
# 定义布尔开关(默认开启优化)
set(ENABLE_OPTIMIZATION ON CACHE BOOL "Enable compiler optimizations")

# 根据开关设置编译标志
if(ENABLE_OPTIMIZATION)
    target_compile_options(my_target PRIVATE -O3)
else()
    target_compile_options(my_target PRIVATE -O0)
endif()

3.自定义用户配置(缓存变量)

cpp 复制代码
# 定义安装路径(默认 /usr/local)
set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "Installation prefix")

# 定义第三方库路径(用户可手动指定)
set(ZLIB_ROOT "" CACHE PATH "Path to ZLIB installation")
find_package(ZLIB REQUIRED PATHS ${ZLIB_ROOT})

4.环境变量设置(构建阶段)

cpp 复制代码
# 设置编译器搜索路径(覆盖系统默认)
set(ENV{PATH} "/opt/gcc-11/bin:$ENV{PATH}")

6.注意事项

1.避免滥用全局变量

  • 普通变量应尽量限制在局部作用域(如子目录或函数内),避免与其他变量冲突。
  • 缓存变量仅用于用户需要自定义的选项(如功能开关、路径配置),而非内部逻辑。

2. 优先使用现代 CMake 命令

  • 对于目标属性(如头文件路径、编译选项),优先使用 target_include_directoriestarget_compile_options 等命令,而非直接设置变量。
  • 示例(反模式):
cpp 复制代码
# 不推荐:直接设置全局变量
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

# 推荐:通过目标属性设置(仅影响特定目标)
target_compile_options(my_target PRIVATE -Wall)

3. 缓存变量的类型与文档

  • 缓存变量必须指定 <type><docstring>,否则 cmake-gui 无法正确显示和修改。
  • 示例(错误):
cpp 复制代码
set(ENABLE_LOGGING ON CACHE)  # 缺少类型和文档,会报错

4.变量作用域的陷阱

  • 子目录中定义的普通变量不会自动传递给父目录,需通过 PARENT_SCOPE 显式传递。
  • 示例(错误):
cpp 复制代码
# 子目录中设置变量
set(SUB_VAR "value")

# 父目录无法直接获取 SUB_VAR(值为空)

7.总结

set 命令是 CMake 中管理变量和配置的基础工具,核心用途包括:

  • 存储项目内部临时数据(普通变量);
  • 定义用户可配置选项(缓存变量);
  • 控制构建环境(环境变量)。

合理使用 set 命令需注意变量类型、作用域和现代 CMake 最佳实践,避免全局变量滥用,优先通过目标属性(target_* 命令)管理编译逻辑。

相关链接

相关推荐
虾球xz6 小时前
CppCon 2016 学习:GAME ENGINE USING C++11
大数据·开发语言·c++·学习
Jet45056 小时前
第100+42步 ChatGPT学习:R语言实现阈值调整
开发语言·学习·chatgpt·r语言
虾球xz6 小时前
CppCon 2016 学习:fixed_point Library
开发语言·c++·学习
希希不嘻嘻~傻希希6 小时前
CSS 字体与文本样式笔记
开发语言·前端·javascript·css·ecmascript
HaiQinyanAN6 小时前
【学习笔记】nlohmannjson&&cjson
c++·笔记·学习·json
C语言小火车6 小时前
【C语言】银行账户管理系统丨源码+解析
c语言·c++·算法·课程设计
寄思~7 小时前
Python学习笔记:错误和异常处理
开发语言·笔记·python·学习
clmm1237 小时前
Java动态生成Nginx服务配置
java·开发语言·nginx
东方芷兰7 小时前
Leetcode 刷题记录 17 —— 堆
java·c++·b树·算法·leetcode·职场和发展
lzb_kkk7 小时前
【MFC】编辑框、下拉框、列表控件
c语言·开发语言·c++·mfc·1024程序员节