第 13 章:高级特性
第 13 章:高级特性
13.1 生成器表达式(Generator Expressions)
生成器表达式在生成阶段求值(而非配置阶段),语法为 $<...>。
13.1.1 为什么需要生成器表达式
# 问题:Multi-Config 生成器(如 VS)在配置阶段不知道最终配置
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(myapp PRIVATE DEBUG_MODE)
endif()
# ❌ 对 Visual Studio 不起作用,因为 CMAKE_BUILD_TYPE 在配置时未设置
# 解决:使用生成器表达式
target_compile_definitions(myapp PRIVATE
$<$<CONFIG:Debug>:DEBUG_MODE>
)
# ✅ 在生成阶段根据实际配置求值
13.1.2 条件表达式
# $<$<条件>:真值>
# $<条件:真值:假值>
# 按配置
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<CONFIG:Release>:NDEBUG>
$<IF:$<CONFIG:Debug>,DEBUG,NDEBUG>
# 按编译器
$<$<CXX_COMPILER_ID:GNU>:HAVE_GCC>
$<$<CXX_COMPILER_ID:MSVC>:HAVE_MSVC>
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall>
# 按平台
$<$<PLATFORM_ID:Linux>:LINUX>
$<$<PLATFORM_ID:Windows>:WIN32>
$<$<PLATFORM_ID:Darwin>:APPLE>
# 按语言
$<$<COMPILE_LANGUAGE:CXX>:__cplusplus>
$<$<COMPILE_LANGUAGE:C>:__STDC__>
# 布尔表达式
$<$<BOOL:${MY_VAR}>:DEFINED_MY_VAR>
$<$<NOT:$<CONFIG:Debug>>:RELEASE_MODE>
$<$<AND:$<CONFIG:Release>,$<CXX_COMPILER_ID:GNU>>:GCC_RELEASE>
$<$<OR:$<PLATFORM_ID:Linux>,$<PLATFORM_ID:Darwin>>:UNIX_LIKE>
13.1.3 字符串操作表达式
# 字符串比较
$<$<STREQUAL:${MY_VAR},hello>:MATCH>
$<$<VERSION_GREATER:${CMAKE_VERSION},3.20>:HAS_FEATURE>
# 转换
$<LOWER_CASE:${MY_VAR}>
$<UPPER_CASE:${MY_VAR}>
# 生成器表达式中的列表
$<JOIN:list,separator>
$<SEPARATOR:;>
13.1.4 目标相关表达式
# 目标文件路径
$<TARGET_FILE:myapp>
$<TARGET_FILE_NAME:myapp>
$<TARGET_FILE_DIR:myapp>
# 目标属性
$<TARGET_PROPERTY:mylib,SOURCES>
$<TARGET_PROPERTY:mylib,INCLUDE_DIRECTORIES>
$<TARGET_NAME_IF_EXISTS:mylib>
# 目标对象文件
$<TARGET_OBJECTS:mylib_objects>
# 目标 PDB 文件(MSVC)
$<TARGET_PDB_FILE:myapp>
13.1.5 安装相关表达式
# 构建时和安装时的路径
target_include_directories(mylib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# 安装时的前缀
$<INSTALL_PREFIX>
$<INSTALL_INTERFACE:include>
13.1.6 常用生成器表达式汇总
| 表达式 | 说明 |
|---|---|
$<CONFIG> | 当前配置名(Debug/Release) |
$<PLATFORM_ID> | 平台标识 |
$<CXX_COMPILER_ID> | C++ 编译器 ID |
$<COMPILE_LANGUAGE> | 编译语言 |
$<BOOL:expr> | 转为布尔值 |
$<IF:cond,t,f> | 条件选择 |
$<TARGET_FILE:tgt> | 目标文件完整路径 |
$<TARGET_OBJECTS:tgt> | 对象文件列表 |
$<BUILD_INTERFACE:expr> | 构建时使用 |
$<INSTALL_INTERFACE:expr> | 安装时使用 |
$<GENEX_EVAL:expr> | 二次求值 |
$<TARGET_GENEX_EVAL:tgt,expr> | 在目标上下文中求值 |
13.2 自定义目标属性
13.2.1 定义自定义属性
# 定义一个全局属性
define_property(TARGET
PROPERTY MY_CUSTOM_VERSION
BRIEF_DOCS "自定义版本号"
FULL_DOCS "用于内部版本追踪的自定义版本号"
)
# 设置属性
set_target_properties(mylib PROPERTIES MY_CUSTOM_VERSION "2.1.0-alpha")
# 获取属性
get_target_property(lib_custom_ver mylib MY_CUSTOM_VERSION)
13.2.2 属性作用域
# TARGET 属性
define_property(TARGET PROPERTY MY_PROP ...)
# DIRECTORY 属性
define_property(DIRECTORY PROPERTY MY_DIR_PROP ...)
# GLOBAL 属性
define_property(GLOBAL PROPERTY MY_GLOBAL_PROP ...)
# SOURCE 属性
define_property(SOURCE PROPERTY MY_SRC_PROP ...)
# TEST 属性
define_property(TEST PROPERTY MY_TEST_PROP ...)
13.3 高级目标依赖
13.3.1 add_dependencies
add_custom_target(generate_headers
COMMAND ${CMAKE_COMMAND} -P generate.cmake
)
add_library(mylib src/mylib.cpp)
add_dependencies(mylib generate_headers)
# mylib 会在 generate_headers 完成后才编译
13.3.2 文件级依赖
# 依赖特定文件
add_custom_command(
OUTPUT generated.cpp
COMMAND generator input.proto
DEPENDS input.proto generator.cmake
VERBATIM
)
13.3.3 跨目录依赖
# 子目录 A
add_library(libA src/a.cpp)
# 子目录 B 依赖 A
add_library(libB src/b.cpp)
target_link_libraries(libB PUBLIC libA) # 自动建立依赖
13.4 生成器表达式实战
13.4.1 条件编译选项
target_compile_options(mylib PRIVATE
# GCC/Clang 专用
$<$<CXX_COMPILER_ID:GNU,Clang>:
-Wall -Wextra -Wpedantic -Wshadow
$<$<CONFIG:Debug>:-g -O0>
$<$<CONFIG:Release>:-O3>
>
# MSVC 专用
$<$<CXX_COMPILER_ID:MSVC>:
/W4 /WX
$<$<CONFIG:Debug>:/Od /Zi>
$<$<CONFIG:Release>:/O2>
>
)
13.4.2 条件定义
target_compile_definitions(mylib PRIVATE
VERSION="${PROJECT_VERSION}"
$<$<CONFIG:Debug>:DEBUG_BUILD>
$<$<PLATFORM_ID:Linux>:PLATFORM_LINUX>
$<$<PLATFORM_ID:Windows>:PLATFORM_WIN32>
$<$<PLATFORM_ID:Darwin>:PLATFORM_MACOS>
$<$<BOOL:${USE_OPENSSL}>:HAS_SSL>
)
13.4.3 条件链接
target_link_libraries(myapp PRIVATE
mylib
$<$<PLATFORM_ID:Linux>:rt>
$<$<PLATFORM_ID:Linux>:dl>
$<$<BOOL:${USE_OPENSSL}>:OpenSSL::SSL>
)
13.5 构建事件
13.5.1 预构建和后构建
add_executable(myapp main.cpp)
# 预构建事件
add_custom_command(TARGET myapp PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "开始构建..."
)
# 预链接事件
add_custom_command(TARGET myapp PRE_LINK
COMMAND ${CMAKE_COMMAND} -E echo "链接中..."
)
# 后构建事件
add_custom_command(TARGET myapp POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "构建完成!"
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:myapp> ${CMAKE_BINARY_DIR}/output/
COMMENT "复制可执行文件到输出目录"
)
13.6 接口库进阶
13.6.1 编译器警告接口库
add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra -Wpedantic -Wshadow -Wconversion>
$<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra -Wpedantic -Wshadow>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
13.6.2 Sanitizer 接口库
add_library(sanitizers INTERFACE)
option(ENABLE_ASAN "AddressSanitizer" OFF)
option(ENABLE_TSAN "ThreadSanitizer" OFF)
option(ENABLE_UBSAN "UndefinedBehaviorSanitizer" OFF)
target_compile_options(sanitizers INTERFACE
$<$<BOOL:${ENABLE_ASAN}>:-fsanitize=address -fno-omit-frame-pointer>
$<$<BOOL:${ENABLE_TSAN}>:-fsanitize=thread>
$<$<BOOL:${ENABLE_UBSAN}>:-fsanitize=undefined>
)
target_link_options(sanitizers INTERFACE
$<$<BOOL:${ENABLE_ASAN}>:-fsanitize=address>
$<$<BOOL:${ENABLE_TSAN}>:-fsanitize=thread>
$<$<BOOL:${ENABLE_UBSAN}>:-fsanitize=undefined>
)
13.6.3 平台特定接口库
add_library(platform_libs INTERFACE)
target_link_libraries(platform_libs INTERFACE
$<$<PLATFORM_ID:Linux>:Threads::Threads;rt;dl>
$<$<PLATFORM_ID:Darwin>:Threads::Threads;"-framework CoreFoundation">
$<$<PLATFORM_ID:Windows>:ws2_32;bcrypt>
)
13.7 对象库进阶
13.7.1 共享对象库
# 多个库共享同一个对象库
add_library(backend_objects OBJECT src/backend.cpp)
target_include_directories(backend_objects PUBLIC include)
target_compile_features(backend_objects PUBLIC cxx_std_17)
# 静态版本
add_library(backend_static STATIC $<TARGET_OBJECTS:backend_objects>)
target_link_libraries(backend_static PUBLIC backend_objects)
# 动态版本
add_library(backend_shared SHARED $<TARGET_OBJECTS:backend_objects>)
target_link_libraries(backend_shared PUBLIC backend_objects)
set_target_properties(backend_shared PROPERTIES POSITION_INDEPENDENT_CODE ON)
13.7.2 对象库条件编译
add_library(mylib_objects OBJECT
src/core.cpp
$<$<PLATFORM_ID:Linux>:src/linux_impl.cpp>
$<$<PLATFORM_ID:Windows>:src/win_impl.cpp>
$<$<PLATFORM_ID:Darwin>:src/mac_impl.cpp>
)
13.8 cmake_language
# 动态调用命令
cmake_language(CALL message STATUS "动态调用 message")
# 延迟调用
cmake_language(DEFER CALL message STATUS "延迟消息")
cmake_language(DEFER DIRECTORY ${CMAKE_SOURCE_DIR} CALL message "在其他目录调用")
# 求值字符串
set(cmd "message")
set(arg "Hello")
cmake_language(EVAL CODE "${cmd}(\"${arg}\")")
13.9 业务场景
场景:灵活的构建配置
# 提供一致的构建体验
add_library(project_options INTERFACE)
# 标准
target_compile_features(project_options INTERFACE cxx_std_17)
# 警告
target_compile_options(project_options INTERFACE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
# Sanitizers
target_link_libraries(project_options INTERFACE
$<$<BOOL:${ENABLE_ASAN}>:sanitizers>
)
# 所有目标使用
add_executable(app main.cpp)
target_link_libraries(app PRIVATE project_options project_warnings)
13.10 注意事项
| 问题 | 说明 |
|---|---|
| 生成器表达式不能用于 if() | if 在配置时执行,生成器表达式在生成时求值 |
| 转义问题 | 生成器表达式中的逗号需要转义 |
| 调试困难 | 无法直接打印生成器表达式 |
| 嵌套复杂性 | 避免过度嵌套,保持可读性 |
13.11 扩展阅读
上一章:第 12 章 — CMake 预设 | 下一章:第 14 章 — 模块系统 →