C/C++ Linux 开发教程(GCC + CMake) / CMake 进阶(函数/宏/Generator Expression)
CMake 进阶(函数/宏/Generator Expression)
掌握 CMake 基础之后,深入了解函数、宏、Generator Expression 和高级特性,能够编写更模块化、可复用的 CMake 代码。
function 与 macro
function 定义与调用
# 定义一个函数
function(print_target_info target)
message(STATUS "Target: ${target}")
message(STATUS " Source dir: ${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS " Binary dir: ${CMAKE_CURRENT_BINARY_DIR}")
get_target_property(type ${target} TYPE)
message(STATUS " Type: ${type}")
get_target_property(sources ${target} SOURCES)
message(STATUS " Sources: ${sources}")
endfunction()
# 调用函数
add_executable(app main.c)
print_target_info(app)
函数参数
function(my_function arg1 arg2 arg3)
message(STATUS "arg1 = ${arg1}")
message(STATUS "arg2 = ${arg2}")
message(STATUS "arg3 = ${arg3}")
message(STATUS "ARGC = ${ARGC}") # 参数个数
message(STATUS "ARGV = ${ARGV}") # 所有参数列表
message(STATUS "ARGN = ${ARGN}") # 多余的参数
endfunction()
my_function(a b c d e)
# arg1 = a, arg2 = b, arg3 = c
# ARGN = d;e (多余参数)
关键字参数(推荐方式)
function(add_my_library)
set(options STATIC SHARED) # 布尔选项
set(oneValueArgs NAME OUTPUT_DIR) # 单值参数
set(multiValueArgs SOURCES HEADERS DEPS) # 多值参数
cmake_parse_arguments(
MYLIB # 前缀
"${options}" # 布尔选项
"${oneValueArgs}" # 单值参数
"${multiValueArgs}" # 多值参数
${ARGN} # 传入的参数
)
# 使用解析后的变量: MYLIB_STATIC, MYLIB_SHARED,
# MYLIB_NAME, MYLIB_SOURCES, MYLIB_HEADERS, MYLIB_DEPS
if(MYLIB_SHARED)
add_library(${MYLIB_NAME} SHARED ${MYLIB_SOURCES})
else()
add_library(${MYLIB_NAME} STATIC ${MYLIB_SOURCES})
endif()
target_link_libraries(${MYLIB_NAME} PRIVATE ${MYLIB_DEPS})
endfunction()
# 调用
add_my_library(
NAME mylib
SHARED
SOURCES src/a.c src/b.c
HEADERS include/a.h include/b.h
DEPS pthread m
)
完整实用函数示例
# cmake/Utils.cmake
# 添加带默认配置的可执行目标
function(add_app_target)
set(options INSTALL)
set(oneValueArgs NAME OUTPUT_DIR)
set(multiValueArgs SOURCES LIBS DEFINES)
cmake_parse_arguments(APP "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT APP_NAME)
message(FATAL_ERROR "add_app_target: NAME is required")
endif()
if(NOT APP_SOURCES)
message(FATAL_ERROR "add_app_target: SOURCES is required")
endif()
add_executable(${APP_NAME} ${APP_SOURCES})
# 默认编译选项
target_compile_options(${APP_NAME} PRIVATE
-Wall -Wextra -Wpedantic
$<$<CXX_COMPILER_ID:GNU>:-Wshadow -Wconversion>
)
# 链接库
if(APP_LIBS)
target_link_libraries(${APP_NAME} PRIVATE ${APP_LIBS})
endif()
# 预处理宏
if(APP_DEFINES)
target_compile_definitions(${APP_NAME} PRIVATE ${APP_DEFINES})
endif()
# 输出目录
if(APP_OUTPUT_DIR)
set_target_properties(${APP_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${APP_OUTPUT_DIR}
)
endif()
# 安装规则
if(APP_INSTALL)
install(TARGETS ${APP_NAME} RUNTIME DESTINATION bin)
endif()
endfunction()
# 使用
add_app_target(
NAME server
INSTALL
SOURCES src/main.c src/server.c
LIBS pthread ssl crypto
DEFINES SERVER_VERSION="1.0"
)
macro 与 function 的区别
# macro: 在调用处展开(类似 C 的 #define)
macro(my_macro arg)
message(STATUS "In macro: arg = ${arg}")
set(${arg} "modified") # 直接修改调用者的变量
endmacro()
# function: 创建新的作用域(类似 C 的函数)
function(my_function arg)
message(STATUS "In function: arg = ${arg}")
set(${arg} "modified" PARENT_SCOPE) # 需要 PARENT_SCOPE 传回
endfunction()
set(my_var "original")
my_macro(my_var)
message(STATUS "After macro: ${my_var}") # "modified"
set(my_var "original")
my_function(my_var)
message(STATUS "After function: ${my_var}") # "original"
set(my_var "original")
my_function(my_var)
message(STATUS "After function with PARENT_SCOPE: ${my_var}") # "modified"
| 特性 | function | macro |
|---|---|---|
| 作用域 | 独立作用域 | 在调用处展开 |
| 变量修改 | 需要 PARENT_SCOPE | 直接修改 |
return() | 退出函数 | 退出调用者 |
| 推荐使用 | ✅ 默认选择 | 谨慎使用 |
CMake 作用域
作用域规则
# 顶层作用域
set(TOP_VAR "top level")
function(outer_function)
# 函数作用域(独立于顶层)
message(STATUS "In outer: TOP_VAR = ${TOP_VAR}") # 可以读取
set(OUTER_VAR "outer level")
message(STATUS "In outer: OUTER_VAR = ${OUTER_VAR}")
function(inner_function)
# 嵌套函数作用域
message(STATUS "In inner: TOP_VAR = ${TOP_VAR}") # 可以读取
message(STATUS "In inner: OUTER_VAR = ${OUTER_VAR}") # 可以读取
set(INNER_VAR "inner level" PARENT_SCOPE) # 传回 outer_function
endfunction()
inner_function()
message(STATUS "After inner: INNER_VAR = ${INNER_VAR}") # 可以访问
endfunction()
outer_function()
# message(STATUS "${OUTER_VAR}") # ❌ 错误:outer_function 作用域外不可见
PARENT_SCOPE 使用
function(set_version)
set(PROJECT_VERSION_MAJOR 2 PARENT_SCOPE)
set(PROJECT_VERSION_MINOR 1 PARENT_SCOPE)
set(PROJECT_VERSION_PATCH 0 PARENT_SCOPE)
endfunction()
set_version()
message(STATUS "Version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
# 输出: Version: 2.1.0
CACHE 变量跨作用域
function(init_cache_var)
# CACHE 变量全局可见(跨越作用域)
if(NOT DEFINED MY_CACHE_VAR)
set(MY_CACHE_VAR "default_value" CACHE STRING "Description")
endif()
endfunction()
init_cache_var()
message(STATUS "MY_CACHE_VAR = ${MY_CACHE_VAR}") # 全局可见
Generator Expression
什么是 Generator Expression
Generator Expression(生成器表达式)是 CMake 在生成构建系统时才求值的表达式。与普通变量不同,它可以感知构建配置、编译器、平台等上下文。
# 语法: $<OPERATOR:VALUE>
# 常用: $<CONFIG:Debug>, $<BOOL:var>, $<IF:cond,true_val,false_val>
条件表达式
# 根据构建类型选择
target_compile_definitions(app PRIVATE
$<$<CONFIG:Debug>:DEBUG_MODE>
$<$<CONFIG:Release>:NDEBUG>
)
# 等价于:
# if (Debug) → -DDEBUG_MODE
# if (Release) → -DNDEBUG
# 多条件
target_compile_options(app PRIVATE
$<$<AND:$<CXX_COMPILER_ID:GNU>,$<CONFIG:Debug>>:-O0 -g3>
$<$<AND:$<CXX_COMPILER_ID:GNU>,$<CONFIG:Release>>:-O3>
)
# IF 表达式(CMake 3.8+)
target_compile_definitions(app PRIVATE
$<$<BOOL:${USE_OPENSSL}>:USE_SSL>
)
# 三元表达式
set_target_properties(app PROPERTIES
POSITION_INDEPENDENT_CODE $<IF:$<BOOL:${BUILD_SHARED_LIBS}>,ON,OFF>
)
配置相关表达式
# 配置名称
message(STATUS "Config: $<CONFIG>")
# 配置比较
$<$<CONFIG:Debug>:...> # Debug 配置
$<$<CONFIG:Release>:...> # Release 配置
$<$<NOT:$<CONFIG:Debug>>:...> # 非 Debug 配置
# 多配置比较
$<$<CONFIG:Debug,RelWithDebInfo>:...> # Debug 或 RelWithDebInfo
目标属性表达式
# 获取目标属性
$<TARGET_PROPERTY:tgt,PROPERTY>
# 获取目标文件路径
$<TARGET_FILE:app> # 可执行文件路径
$<TARGET_FILE_NAME:app> # 文件名
$<TARGET_FILE_DIR:app> # 文件所在目录
# 获取目标 SONAME
$<TARGET_SONAME_FILE:mylib> # libmylib.so.1
$<TARGET_SONAME_FILE_NAME:mylib> # libmylib.so.1
# 获取目标 PDB 文件(Windows)
$<TARGET_PDB_FILE:app>
字符串操作表达式
# 转换
$<LOWER_CASE:string> # 小写
$<UPPER_CASE:string> # 大写
$<MAKE_C_IDENTIFIER:string> # 转为 C 标识符
# 生成器表达式中的字符串
$<GENEX_EVAL:expr> # 递归求值
$<TARGET_GENEX_EVAL:tgt,expr> # 在目标上下文中求值
# 条件替换
$<IF:condition,true_string,false_string>
路径和文件表达式
# 当前源码/构建目录
$<TARGET_PROPERTY:SOURCE_DIR>
$<TARGET_PROPERTY:BINARY_DIR>
# 安装前缀
$<INSTALL_PREFIX>
# 连接多个路径
$<PATH:CMAKE_CURRENT_SOURCE_DIR,include>
实用组合示例
# 为不同配置设置不同的输出目录
set_target_properties(app PROPERTIES
RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/debug/bin
RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/release/bin
)
# 使用 Generator Expression 实现相同效果
set_target_properties(app PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<CONFIG>/bin
)
# 条件链接库
target_link_libraries(app PRIVATE
$<$<BOOL:${USE_OPENSSL}>:OpenSSL::SSL>
$<$<BOOL:${USE_OPENSSL}>:OpenSSL::Crypto>
$<$<PLATFORM_ID:Linux>:rt>
$<$<PLATFORM_ID:Linux>:dl>
)
# 仅对 C++ 文件添加选项
target_compile_options(app PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-std=c++17>
$<$<COMPILE_LANGUAGE:C>:-std=c11>
)
自定义命令与目标
add_custom_command
# 生成文件的自定义命令(在构建时执行)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.h
COMMAND python3 ${CMAKE_SOURCE_DIR}/scripts/gen_header.py
--input ${CMAKE_CURRENT_SOURCE_DIR}/config.json
--output ${CMAKE_CURRENT_BINARY_DIR}/generated.h
DEPENDS ${CMAKE_SOURCE_DIR}/config.json
${CMAKE_SOURCE_DIR}/scripts/gen_header.py
COMMENT "Generating generated.h from config.json"
VERBATIM
)
# 构建后执行的自定义命令(附加到目标)
add_custom_command(TARGET app POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo "Build completed!"
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:app>
${CMAKE_BINARY_DIR}/dist/$<TARGET_FILE_NAME:app>
COMMENT "Copying app to dist directory"
)
add_custom_target
# 自定义目标(不产生输出文件)
add_custom_target(docs
COMMAND doxygen ${CMAKE_SOURCE_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Generating documentation with Doxygen"
)
# 自定义目标依赖其他目标
add_custom_target(coverage
COMMAND lcov --capture --directory . --output-file coverage.info
COMMAND genhtml coverage.info --output-directory coverage_report
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Generating code coverage report"
)
DEPENDS app test_app # 确保先构建这些目标
自定义命令 + 自定义目标组合
# 生成 protobuf 代码
find_program(PROTOC protoc)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/message.pb.cc
${CMAKE_CURRENT_BINARY_DIR}/message.pb.h
COMMAND ${PROTOC}
--cpp_out=${CMAKE_CURRENT_BINARY_DIR}
--proto_path=${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/message.proto
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/message.proto
COMMENT "Compiling message.proto"
VERBATIM
)
# 将生成的文件加入目标
add_library(proto_gen OBJECT
${CMAKE_CURRENT_BINARY_DIR}/message.pb.cc
)
target_include_directories(proto_gen PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
属性系统
目标属性
# 设置属性
set_target_properties(app PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE ON
INTERPROCEDURAL_OPTIMIZATION ON
)
# 获取属性
get_target_property(app_type app TYPE)
message(STATUS "Type: ${app_type}")
# 自定义属性
set_property(TARGET app PROPERTY MY_CUSTOM_PROP "value")
get_property(result TARGET app PROPERTY MY_CUSTOM_PROP)
源文件属性
# 设置源文件特定的编译选项
set_source_files_properties(
src/generated.c
PROPERTIES COMPILE_FLAGS "-w" # 禁用警告
)
# 标记为生成的文件(跳过依赖扫描)
set_source_files_properties(
${CMAKE_CURRENT_BINARY_DIR}/generated.c
PROPERTIES GENERATED TRUE
)
# 设置源文件的编译定义
set_source_files_properties(
src/feature_x.c
PROPERTIES COMPILE_DEFINITIONS "FEATURE_X_ENABLED"
)
目录属性
# 获取目录属性
get_property(inc_dirs DIRECTORY PROPERTY INCLUDE_DIRECTORIES)
message(STATUS "Include dirs: ${inc_dirs}")
# 获取所有定义
get_property(defs DIRECTORY PROPERTY COMPILE_DEFINITIONS)
message(STATUS "Definitions: ${defs}")
全局属性
# 获取所有目标
get_property(all_targets GLOBAL PROPERTY TARGETS)
message(STATUS "All targets: ${all_targets}")
# 获取所有源文件
get_property(all_sources GLOBAL PROPERTY SOURCES)
# 自定义全局属性
set_property(GLOBAL PROPERTY MY_GLOBAL_LIST "a;b;c")
get_property(result GLOBAL PROPERTY MY_GLOBAL_LIST)
install 规则进阶
基本 install 规则
# 安装目标
install(TARGETS app mylib
RUNTIME DESTINATION bin # 可执行文件
LIBRARY DESTINATION lib # .so 文件
ARCHIVE DESTINATION lib # .a 文件
INCLUDES DESTINATION include # 头文件目录
PUBLIC_HEADER DESTINATION include # 公共头文件
)
# 安装文件
install(FILES
include/mylib.h
include/utils.h
DESTINATION include
)
# 安装目录
install(DIRECTORY config/ DESTINATION etc/myapp
PATTERN "*.in" EXCLUDE # 排除 .in 文件
PATTERN ".git" EXCLUDE # 排除 .git
)
export 与 config 文件
# 导出目标配置
install(TARGETS mylib
EXPORT mylibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
INCLUDES DESTINATION include
)
# 生成并安装 Config 文件
install(EXPORT mylibTargets
FILE mylibTargets.cmake
NAMESPACE mylib::
DESTINATION lib/cmake/mylib
)
# 生成 Config.cmake
include(CMakePackageConfigHelpers)
configure_package_config_file(
${CMAKE_CURRENT_SOURCE_DIR}/cmake/mylibConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/mylibConfig.cmake
INSTALL_DESTINATION lib/cmake/mylib
)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/mylibConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mylibConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/mylibConfigVersion.cmake
DESTINATION lib/cmake/mylib
)
组件安装
install(TARGETS app
RUNTIME DESTINATION bin COMPONENT runtime
)
install(TARGETS mylib
LIBRARY DESTINATION lib COMPONENT libraries
ARCHIVE DESTINATION lib COMPONENT libraries
)
install(FILES include/mylib.h
DESTINATION include COMPONENT development
)
# 按组件安装
# cmake --install build --component runtime
# cmake --install build --component development
CTest 集成
基本测试配置
enable_testing()
# 简单测试
add_executable(test_basic test/test_basic.c)
target_link_libraries(test_basic PRIVATE mylib)
add_test(NAME test_basic COMMAND test_basic)
# 带参数的测试
add_test(NAME test_with_args
COMMAND test_basic --verbose --timeout 10
)
# 设置测试属性
set_tests_properties(test_basic PROPERTIES
TIMEOUT 30 # 超时秒数
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
ENVIRONMENT "TEST_DATA_DIR=${CMAKE_SOURCE_DIR}/test/data"
LABELS "unit;fast"
)
# 测试应该失败(期望失败的测试)
add_test(NAME test_should_fail COMMAND app --invalid-arg)
set_tests_properties(test_should_fail PROPERTIES WILL_FAIL TRUE)
fixture 测试
# 设置测试 fixture(setup/teardown)
add_test(NAME setup_db COMMAND ${CMAKE_COMMAND} -E touch test.db)
set_tests_properties(setup_db PROPERTIES FIXTURES_SETUP db_fixture)
add_test(NAME test_with_db COMMAND test_db_app)
set_tests_properties(test_with_db PROPERTIES FIXTURES_REQUIRED db_fixture)
add_test(NAME cleanup_db COMMAND ${CMAKE_COMMAND} -E remove test.db)
set_tests_properties(cleanup_db PROPERTIES FIXTURES_CLEANUP db_fixture)
跨平台配置
平台检测
# 操作系统检测
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Building for Linux")
target_compile_definitions(app PRIVATE PLATFORM_LINUX)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
message(STATUS "Building for macOS")
target_compile_definitions(app PRIVATE PLATFORM_MACOS)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
message(STATUS "Building for Windows")
target_compile_definitions(app PRIVATE PLATFORM_WINDOWS)
endif()
# 更简洁的方式
if(UNIX AND NOT APPLE)
message(STATUS "Linux/Unix")
endif()
if(WIN32)
message(STATUS "Windows")
endif()
if(APPLE)
message(STATUS "Apple (macOS/iOS)")
endif()
编译器检测
# 编译器检测
if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
message(STATUS "GCC compiler")
target_compile_options(app PRIVATE
-Wall -Wextra -Wpedantic
)
elseif(CMAKE_C_COMPILER_ID STREQUAL "Clang")
message(STATUS "Clang compiler")
target_compile_options(app PRIVATE
-Wall -Wextra -Weverything
)
elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
message(STATUS "MSVC compiler")
target_compile_options(app PRIVATE /W4)
endif()
# 使用生成器表达式(更推荐)
target_compile_options(app PRIVATE
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
头文件和功能检测
include(CheckIncludeFile)
include(CheckFunctionExists)
include(CheckTypeSize)
# 检测头文件
check_include_file("sys/epoll.h" HAVE_EPOLL)
check_include_file("sys/event.h" HAVE_KQUEUE)
check_include_file("openssl/ssl.h" HAVE_OPENSSL)
if(HAVE_EPOLL)
target_compile_definitions(app PRIVATE HAVE_EPOLL)
endif()
# 检测函数
check_function_exists(mmap HAVE_MMAP)
check_function_exists(pthread_create HAVE_PTHREAD)
# 检测类型大小
check_type_size("void*" SIZEOF_VOID_P)
message(STATUS "sizeof(void*) = ${SIZEOF_VOID_P}")
生成配置头文件
# config.h.in
#cmakedefine HAVE_EPOLL
#cmakedefine HAVE_KQUEUE
#cmakedefine HAVE_OPENSSL
#cmakedefine HAVE_MMAP
#cmakedefine SIZEOF_VOID_P @SIZEOF_VOID_P@
#cmakedefine PROJECT_VERSION "@PROJECT_VERSION@"
# CMakeLists.txt
configure_file(
${CMAKE_SOURCE_DIR}/config.h.in
${CMAKE_BINARY_DIR}/config.h
)
target_include_directories(app PRIVATE ${CMAKE_BINARY_DIR})
CMake 模块编写
模块基本结构
# cmake/FindMyLib.cmake — 查找模块
include(FindPackageHandleStandardArgs)
# 查找头文件
find_path(MYLIB_INCLUDE_DIR
NAMES mylib.h
PATHS
/usr/local/include
/usr/include
${MYLIB_ROOT}/include
)
# 查找库文件
find_library(MYLIB_LIBRARY
NAMES mylib
PATHS
/usr/local/lib
/usr/lib
${MYLIB_ROOT}/lib
)
# 处理查找结果
find_package_handle_standard_args(MyLib
DEFAULT_MSG
MYLIB_LIBRARY
MYLIB_INCLUDE_DIR
)
# 创建导入目标
if(MyLib_FOUND AND NOT TARGET MyLib::MyLib)
add_library(MyLib::MyLib UNKNOWN IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "${MYLIB_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_INCLUDE_DIR}"
)
endif()
mark_as_advanced(MYLIB_LIBRARY MYLIB_INCLUDE_DIR)
# 使用自定义模块
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
find_package(MyLib REQUIRED)
target_link_libraries(app PRIVATE MyLib::MyLib)
⚠️ 注意点
- 优先使用 function:macro 没有作用域隔离,容易产生副作用
- Generator Expression 求值时机:只能用于特定命令参数,不能用于普通
if()判断 - PARENT_SCOPE 不传播到孙作用域:只影响直接父作用域
- install export 路径:使用相对路径以便可重定位安装
- CMake 版本兼容:使用新特性前检查
cmake_minimum_required
💡 提示
- 调试 CMake 脚本:使用
message(STATUS ...)或cmake --trace - 函数返回值:通过
set(result "value" PARENT_SCOPE)实现 - 缓存变量默认值:使用
if(NOT DEFINED VAR)检查 - Generator Expression 列表:使用
$<SEMICOLON>转义分号 - 模块化 CMake:将常用的函数/宏放在
cmake/目录,用include()引入
工程场景
场景 1:创建可复用的库 CMake 模块
# cmake/MyLibHelpers.cmake
function(mylib_add_library NAME)
set(options HEADER_ONLY)
set(oneValueArgs NAMESPACE)
set(multiValueArgs SOURCES PUBLIC_HEADERS PRIVATE_HEADERS DEPS)
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(ARG_HEADER_ONLY)
add_library(${NAME} INTERFACE)
target_include_directories(${NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
else()
add_library(${NAME} ${ARG_SOURCES} ${ARG_PUBLIC_HEADERS} ${ARG_PRIVATE_HEADERS})
target_include_directories(${NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
if(ARG_DEPS)
target_link_libraries(${NAME} PUBLIC ${ARG_DEPS})
endif()
endif()
if(ARG_NAMESPACE)
add_library(${ARG_NAMESPACE}::${NAME} ALIAS ${NAME})
endif()
endfunction()
场景 2:条件编译特性模块
# cmake/FeatureDetection.cmake
include(CheckIncludeFile)
include(CMakePushCheckState)
function(detect_features target)
cmake_push_check_state()
set(CMAKE_REQUIRED_FLAGS "${CMAKE_C_FLAGS}")
check_include_file("sys/epoll.h" HAVE_EPOLL)
check_include_file("sys/event.h" HAVE_KQUEUE)
check_include_file("sys/sendfile.h" HAVE_SENDFILE)
if(HAVE_EPOLL)
target_compile_definitions(${target} PRIVATE HAVE_EPOLL)
message(STATUS " ✓ epoll support")
endif()
if(HAVE_KQUEUE)
target_compile_definitions(${target} PRIVATE HAVE_KQUEUE)
message(STATUS " ✓ kqueue support")
endif()
cmake_pop_check_state()
endfunction()