CMake 系列教程(三):变量、条件与控制流
2026/6/11 10:16:55 网站建设 项目流程

CMake 系列教程(三):变量、条件与控制流

让你的构建脚本"聪明"起来


一、变量基础

1.1 定义与使用

# 定义普通变量 set(MY_NAME "CMake") set(MY_VERSION 3) # 使用变量:${变量名} message(STATUS "Project: ${MY_NAME}, Version: ${MY_VERSION}") # -- Project: CMake, Version: 3

变量在 CMake 中本质是字符串,没有类型区分。

1.2 列表

CMake 通过分号;分隔实现列表:

# 两种等价写法 set(SOURCES a.cpp b.cpp c.cpp) # 空格分隔,自动转为分号 set(SOURCES "a.cpp;b.cpp;c.cpp") # 显式分号 # 结果相同:SOURCES = "a.cpp;b.cpp;c.cpp" # 使用列表 add_executable(myapp ${SOURCES}) # 展开为:add_executable(myapp a.cpp b.cpp c.cpp)
列表操作
# 追加元素 list(APPEND SOURCES d.cpp e.cpp) # SOURCES = "a.cpp;b.cpp;c.cpp;d.cpp;e.cpp" # 在开头插入 list(INSERT SOURCES 0 main.cpp) # 删除元素 list(REMOVE_ITEM SOURCES c.cpp) # 获取长度 list(LENGTH SOURCES COUNT) message(STATUS "Source count: ${COUNT}") # 排序 list(SORT SOURCES)

1.3 变量作用域

CMake 变量遵循函数作用域规则:

set(X "top-level") function(my_func) message(STATUS "Inside func, X = ${X}") # top-level(可读取外部变量) set(X "inside-func") # 仅在函数内修改,不影响外部 message(STATUS "After set, X = ${X}") # inside-func endfunction() my_func() message(STATUS "After func, X = ${X}") # top-level(函数内的修改未传播)

从函数内部修改外部变量需要用PARENT_SCOPE

function(my_func) set(X "modified" PARENT_SCOPE) # 修改调用者的 X endfunction()

⚠️add_subdirectory引入的子CMakeLists.txt也是一个新作用域,子目录修改的变量不会影响父目录(除非用PARENT_SCOPE)。


二、缓存变量

2.1 普通变量 vs 缓存变量

CMake 有两套独立的变量系统:

特性普通变量缓存变量
作用域函数/目录作用域全局持久
存储位置内存CMakeCache.txt
生命周期配置阶段结束即消失跨多次配置保留
设置方式set(VAR value)set(VAR value CACHE TYPE "")
优先级高于缓存变量低于普通变量
# 缓存变量 set(BUILD_TESTS ON CACHE BOOL "Whether to build tests") # 第一次配置:写入 CMakeCache.txt # 后续配置:不覆盖已有缓存值(除非 FORCE)

2.2 缓存变量类型

类型用途在 cmake-gui 中的表现
BOOL开关复选框
STRING字符串文本框
FILEPATH文件路径文件选择器
PATH目录路径目录选择器

2.3 修改缓存变量

# 命令行方式cmake-Bbuild-DBUILD_TESTS=OFF# 交互式方式ccmake build/# 终端 TUIcmake-gui build/# 图形界面(Windows)

2.4option命令

optionBOOL类型缓存变量的语法糖:

# 等价写法 option(BUILD_TESTS "Build test programs" ON) # set(BUILD_TESTS ON CACHE BOOL "Build test programs")

💡option一定要在project()之后调用,否则ON/OFF可能与缓存中的已有值冲突。


三、条件判断

3.1 基本语法

if(CONDITION) # ... elseif(ANOTHER_CONDITION) # ... else() # ... endif()

3.2 常用条件表达式

布尔判断
# 以下值为"假":OFF, NO, FALSE, 0, N, IGNORE, NOTFOUND, 空字符串, 以 -NOTFOUND 结尾 # 其余为"真" if(BUILD_TESTS) message(STATUS "Tests enabled") endif()
比较
# 数值比较 if(${PROJECT_VERSION_MAJOR} GREATER 2) # 字符串比较 if(CMAKE_SYSTEM_NAME STREQUAL "Linux") # 版本比较 if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
操作符含义
EQUAL/LESS/GREATER数值比较
STREQUAL/STRLESS/STRGREATER字符串比较
VERSION_EQUAL/VERSION_GREATER/VERSION_LESS版本号比较
逻辑组合
if(UNIX AND NOT APPLE) # Linux 环境 endif() if(WIN32 OR CYGWIN) # Windows 环境 endif()
平台判断
if(WIN32) # Windows(含 64 位) if(UNIX) # Linux / macOS / BSD if(APPLE) # macOS / iOS if(MSVC) # Microsoft Visual C++ if(CMAKE_SIZEOF_VOID_P EQUAL 8) # 64 位 endif()
文件系统
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/config.h") message(STATUS "config.h found") endif() if(IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include") message(STATUS "include directory exists") endif()

3.3 常见陷阱

set(VAR "OFF") # ❌ 错误:永远为真,因为 "OFF" 是非空字符串 if(${VAR}) # ✅ 正确:展开后变成 if(OFF),进行布尔判断 if(${VAR}) # ✅ 更推荐:使用变量名,让 if 自动求值 if(VAR)

💡最佳实践:在if()中直接写变量名(不加${}),让 CMake 自动处理布尔语义。仅当需要字符串比较时才用${}


四、循环

4.1foreach

# 遍历列表 set(LANGS C CXX CUDA) foreach(lang IN LISTS LANGS) message(STATUS "Language: ${lang}") endforeach() # 遍历值 foreach(i RANGE 1 5) # 1, 2, 3, 4, 5 message(STATUS "i = ${i}") endforeach() foreach(i RANGE 0 10 3) # 0, 3, 6, 9(步长为 3) message(STATUS "i = ${i}") endforeach() # 同时遍历多个列表 set(NAMES alpha beta gamma) set(VALUES 1 2 3) foreach(name val IN ZIP_LISTS NAMES VALUES) message(STATUS "${name} = ${val}") endforeach() # alpha = 1, beta = 2, gamma = 3

4.2while

set(COUNT 0) while(COUNT LESS 5) math(EXPR COUNT "${COUNT} + 1") message(STATUS "Count: ${COUNT}") endwhile()

4.3 循环控制

foreach(i RANGE 1 10) if(i EQUAL 5) continue() # 跳过本次迭代 endif() if(i EQUAL 8) break() # 跳出循环 endif() message(STATUS "i = ${i}") endforeach() # 输出:1, 2, 3, 4, 6, 7

五、函数与宏

5.1function

function(add_my_library name) # ARGN:所有额外参数 # ARGC:参数总数 # ARGV:所有参数列表 # ARGV0, ARGV1, ...:按位置访问 message(STATUS "Creating library: ${name}") message(STATUS "Sources: ${ARGN}") add_library(${name} STATIC ${ARGN}) target_include_directories(${name} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) target_compile_features(${name} PUBLIC cxx_std_17) endfunction() # 调用 add_my_library(math math/add.cpp math/sub.cpp) # 创建一个名为 math 的静态库

函数内部是独立作用域,修改的变量默认不传播到外部。

5.2macro

macro(my_macro arg) # 宏是文本替换,不做作用域隔离 message(STATUS "Macro arg: ${arg}") endmacro()

函数 vs 宏

特性functionmacro
作用域独立调用者作用域
参数传递值传递(副本)文本替换
return()跳出函数跳出包含宏的整个函数
推荐度✅ 优先使用仅当需要修改调用者变量时

⚠️强烈建议:除非有特殊需求,一律使用 function,避免宏的隐式作用域问题。


六、configure_file:生成配置头文件

6.1 问题场景

代码中需要用到版本号、构建类型等信息,但不能硬编码——这些值在 CMake 配置阶段才能确定。

6.2 解决方案

config.h.in(模板文件):

#pragmaonce#definePROJECT_VERSION"@PROJECT_VERSION@"#definePROJECT_NAME"@PROJECT_NAME@"#cmakedefineENABLE_LOGGING#cmakedefine01HAVE_OPENSSL// 使用 configure 变量#defineDATA_DIR"@CMAKE_INSTALL_PREFIX@/share/@PROJECT_NAME@"

CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(MyApp VERSION 2.1.0 LANGUAGES CXX) option(ENABLE_LOGGING "Enable logging" ON) # 查找 OpenSSL(可选) find_package(OpenSSL) # 生成 config.h configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY # 只替换 @VAR@ 形式,不替换 ${VAR} 形式 ) # 使用生成的头文件 add_executable(myapp main.cpp) target_include_directories(myapp PRIVATE ${CMAKE_CURRENT_BINARY_DIR} # 包含生成的 config.h )

生成的 config.h(假设 ENABLE_LOGGING=ON, OpenSSL 已安装):

#pragmaonce#definePROJECT_VERSION"2.1.0"#definePROJECT_NAME"MyApp"#defineENABLE_LOGGING#defineHAVE_OPENSSL1#defineDATA_DIR"/usr/local/share/MyApp"

6.3#cmakedefine规则

模板写法变量为真变量为假
#cmakedefine VAR#define VAR/* #undef VAR */
#cmakedefine01 VAR#define VAR 1#define VAR 0

七、实用模式

7.1 多配置构建类型判断

# 兼容单配置和多配置生成器 get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(isMultiConfig) # Visual Studio / Ninja Multi-Config message(STATUS "Multi-config generator") else() # Makefile / Ninja (单配置) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) endif() endif()

7.2 平台适配编译选项

function(set_default_compile_options target) target_compile_features(${target} PUBLIC cxx_std_17) if(MSVC) target_compile_options(${target} PRIVATE /W4 /utf-8) else() target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -Werror ) endif() endfunction() # 使用 add_executable(myapp main.cpp) set_default_compile_options(myapp)

7.3 条件编译源文件

set(APP_SOURCES main.cpp app.cpp) if(WIN32) list(APPEND APP_SOURCES platform/win.cpp) elseif(UNIX AND NOT APPLE) list(APPEND APP_SOURCES platform/linux.cpp) elseif(APPLE) list(APPEND APP_SOURCES platform/macos.cpp) endif() add_executable(myapp ${APP_SOURCES})

小结

知识点要点
变量字符串本质,${}引用,函数作用域
列表分号分隔,list()操作
缓存变量CACHE类型,CMakeCache.txtoption
条件if/elseif/else/endif,推荐变量名不加${}
循环foreach为主,RANGEZIP_LISTS
函数独立作用域,优先于宏
configure_file模板生成配置头文件,#cmakedefine

📖下一期预告:《CMake 系列教程(四):依赖管理》—— 从find_packageFetchContent,解决 C/C++ 项目最头疼的第三方库集成问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询