强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 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 章 — 模块系统 →