CMake宏详解
宏macro和函数function类似但有本质区别。宏在编译时进行文本替换不创建独立作用域。宏的基本语法定义和调用# 定义宏 macro(my_macro arg1 arg2) message(Argument 1: ${arg1}) message(Argument 2: ${arg2}) message(All arguments: ${ARGN}) endmacro() # 调用宏 my_macro(hello world extra args)宏 vs 函数的核心区别# 宏 - 文本替换无作用域 macro(macro_test value) set(result ${value}) set(modified changed) endmacro() # 函数 - 独立作用域 function(function_test value) set(result ${value}) set(modified changed) endfunction() set(result original) set(modified original) macro_test(from macro) message(Macro - result: ${result}) # from macro (改变了) message(Macro - modified: ${modified}) # changed (改变了) set(result original) set(modified original) function_test(from function) message(Function - result: ${result}) # original (未改变) message(Function - modified: ${modified}) # original (未改变)宏的高级特性直接访问调用者变量macro(assert_defined var_name) if(NOT DEFINED ${var_name}) message(FATAL_ERROR Variable ${var_name} is not defined) else() message(${var_name} ${${var_name}}) endif() endmacro() # 使用 set(MY_VAR some value) assert_defined(MY_VAR) # MY_VAR some value assert_defined(NONEXISTENT) # 错误修改调用者变量macro(append_to_list list_var) foreach(item ${ARGN}) set(${list_var} ${${list_var}} ${item}) endforeach() endmacro() set(my_list a b c) append_to_list(my_list d e f) message(${my_list}) # a b c d e f条件执行代码块macro(if_msvc) if(MSVC) ${ARGN} endif() endmacro() macro(if_unix) if(UNIX) ${ARGN} endif() endmacro() # 使用 if_msvc( message(Visual Studio detected) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) ) if_unix( message(Unix-like system) add_compile_options(-Wall -Wextra) )实际应用示例调试宏macro(debug_print prefix) if(CMAKE_BUILD_TYPE STREQUAL Debug) message(${prefix}: ${ARGN}) endif() endmacro() macro(trace_enter) debug_print(Entering ${CMAKE_CURRENT_FUNCTION} ${CMAKE_CURRENT_LIST_FILE}:${CMAKE_CURRENT_LIST_LINE}) endmacro() macro(trace_exit) debug_print(Exiting ${CMAKE_CURRENT_FUNCTION}) endmacro() # 使用 function(my_function) trace_enter() # 函数体 set(value 42) debug_print(Value is ${value}) trace_exit() endfunction()代码生成宏macro(define_config_variables) foreach(var ${ARGN}) if(DEFINED ${var}) add_compile_definitions(${var}${${var}}) message(STATUS Defining ${var}${${var}}) else() message(WARNING Variable ${var} not defined) endif() endforeach() endmacro() # 使用 set(VERSION 1.2.3) set(BUILD_NUMBER 42) set(DEBUG_MODE ON) define_config_variables(VERSION BUILD_NUMBER DEBUG_MODE) # 生成: -DVERSION1.2.3 -DBUILD_NUMBER42 -DDEBUG_MODEON简化重复代码# 创建多个相似目标 macro(create_test_executable test_name) set(test_exe test_${test_name}) add_executable(${test_exe} tests/${test_name}.cpp) target_link_libraries(${test_exe} PRIVATE ${PROJECT_NAME} gtest gtest_main) add_test(NAME ${test_name} COMMAND ${test_exe}) if(CMAKE_BUILD_TYPE STREQUAL Debug) set_target_properties(${test_exe} PROPERTIES COMPILE_FLAGS -g -O0) endif() endmacro() # 批量创建测试 create_test_executable(math) create_test_executable(string) create_test_executable(container)平台检测宏macro(detect_platform) if(WIN32) set(PLATFORM Windows) set(PLATFORM_LIB_SUFFIX .dll) elseif(APPLE) set(PLATFORM macOS) set(PLATFORM_LIB_SUFFIX .dylib) elseif(UNIX) set(PLATFORM Linux) set(PLATFORM_LIB_SUFFIX .so) else() set(PLATFORM Unknown) message(WARNING Unknown platform) endif() message(STATUS Detected platform: ${PLATFORM}) endmacro() macro(add_platform_defines) detect_platform() add_compile_definitions(PLATFORM_${PLATFORM}) if(WIN32) add_compile_definitions(WIN32_LEAN_AND_MEAN) elseif(UNIX) add_compile_definitions(_GNU_SOURCE) endif() endmacro()自动生成源文件列表macro(auto_sources dir pattern out_var) file(GLOB_RECURSE ${out_var} ${dir}/${pattern}) # 过滤掉特定文件 set(excluded_patterns ${ARGN}) foreach(exclude ${excluded_patterns}) list(FILTER ${out_var} EXCLUDE REGEX ${exclude}) endforeach() message(STATUS Found ${${out_var}} sources in ${dir}) endmacro() # 使用 auto_sources(src *.cpp SRC_FILES test_.* .*_unittest\\.cpp) auto_sources(include *.h HEADER_FILES) add_executable(myapp ${SRC_FILES} ${HEADER_FILES})宏中的特殊变量${ARGN} - 额外参数macro(print_args) message(All extra arguments: ${ARGN}) set(count 0) foreach(arg ${ARGN}) math(EXPR count ${count} 1) message( Argument ${count}: ${arg}) endforeach() endmacro() print_args(a b c d) # 打印所有参数${ARGC} - 参数数量macro(check_arg_count expected) if(NOT ${ARGC} EQUAL ${expected} 1) # 1 因为宏名也算 message(FATAL_ERROR Expected ${expected} arguments, got ${ARGC}) endif() endmacro() macro(require_args) if(${ARGC} LESS 2) message(FATAL_ERROR At least 1 argument required) endif() endmacro()${ARGV} - 参数数组macro(forward_args target_function) # 转发所有参数 ${target_function}(${ARGV}) endmacro() macro(process_args) set(result ) foreach(i RANGE 1 ${ARGC}) list(APPEND result ARGV${i}${ARGV${i}}) endforeach() message(${result}) endmacro()高级技巧宏中的字符串操作macro(to_upper var) string(TOUPPER ${${var}} ${var}) endmacro() macro(to_lower var) string(TOLOWER ${${var}} ${var}) endmacro() macro(strip_whitespace var) string(STRIP ${${var}} ${var}) endmacro() # 使用 set(name Hello World ) strip_whitespace(name) to_upper(name) message(${name}) # HELLO WORLD条件宏扩展macro(optional_feature feature_name) if(${feature_name}_ENABLED) message(STATUS Enabling ${feature_name}) add_compile_definitions(HAVE_${feature_name}1) ${ARGN} # 执行额外代码 else() message(STATUS Disabling ${feature_name}) endif() endmacro() # 使用 set(OPENGL_ENABLED ON) optional_feature(OPENGL find_package(OpenGL REQUIRED) target_link_libraries(myapp OpenGL::GL) )宏组合macro(begin_group name) message(STATUS ${name} ) set(_group_depth ${_group_depth}1) endmacro() macro(end_group) math(EXPR _group_depth ${_group_depth}-1) if(_group_depth EQUAL 0) message(STATUS ) endif() endmacro() macro(log_message level msg) if(${level} GREATER ${LOG_LEVEL}) message(${level}: ${msg}) endif() endmacro() # 使用 set(LOG_LEVEL 2) begin_group(Build Configuration) log_message(1 Low priority) log_message(2 Medium priority) log_message(3 High priority) end_group()实际项目示例完整的构建配置宏macro(configure_project) # 设置默认值 if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING Build type FORCE) endif() # 设置输出目录 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # 编译选项 if(MSVC) add_compile_options(/W4 /WX) else() add_compile_options(-Wall -Wextra -Wpedantic) endif() # C 标准 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) message(STATUS Project configured for ${CMAKE_BUILD_TYPE} build) endmacro() # 使用 configure_project()测试辅助宏macro(add_unittest test_name) set(test_target ${test_name}_test) set(test_source tests/${test_name}.cpp) add_executable(${test_target} ${test_source}) target_link_libraries(${test_target} PRIVATE ${PROJECT_NAME} GTest::gtest GTest::gtest_main ) add_test(NAME ${test_name} COMMAND ${test_target}) # 设置超时 set_tests_properties(${test_name} PROPERTIES TIMEOUT 10) if(CMAKE_BUILD_TYPE STREQUAL Debug) target_compile_definitions(${test_target} PRIVATE DEBUG_TEST1) endif() message(STATUS Added test: ${test_name}) endmacro() macro(add_unittest_with_data test_name data_dir) add_unittest(${test_name}) # 复制测试数据 add_custom_command(TARGET ${test_name}_test POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/${data_dir} ${CMAKE_CURRENT_BINARY_DIR}/${data_dir} ) endmacro() # 使用 add_unittest(math) add_unittest_with_data(io test_data)安装宏macro(install_project) set(oneValueArgs VERSION LIBRARY_NAME) cmake_parse_arguments(INSTALL ${oneValueArgs} ${ARGN}) if(NOT INSTALL_VERSION) set(INSTALL_VERSION ${PROJECT_VERSION}) endif() if(NOT INSTALL_LIBRARY_NAME) set(INSTALL_LIBRARY_NAME ${PROJECT_NAME}) endif() # 安装库 install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}Targets LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin INCLUDES DESTINATION include ) # 安装头文件 install(DIRECTORY include/ DESTINATION include/${PROJECT_NAME} FILES_MATCHING PATTERN *.h PATTERN *.hpp ) # 生成 CMake 配置 include(CMakePackageConfigHelpers) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake INSTALL_DESTINATION lib/cmake/${PROJECT_NAME} ) install(EXPORT ${PROJECT_NAME}Targets FILE ${PROJECT_NAME}Targets.cmake DESTINATION lib/cmake/${PROJECT_NAME} ) message(STATUS Installation configured for ${PROJECT_NAME} v${INSTALL_VERSION}) endmacro()实践和注意事项命名规范# 使用前缀避免冲突 macro(mylib_add_executable name) # ... endmacro() macro(mylib_target_link_libraries target) # ... endmacro()避免副作用# 不好的做法 - 修改调用者变量 macro(bad_macro) set(temp changed) # 会影响外部 endmacro() # 好的做法 - 使用局部变量名 macro(good_macro) set(_mac_temp local) # 使用特殊前缀 # 或者使用函数而不是宏 endmacro()调试宏macro(debug_macro) message( Macro Debug ) message(Macro name: ${CMAKE_CURRENT_FUNCTION}) message(File: ${CMAKE_CURRENT_LIST_FILE}) message(Line: ${CMAKE_CURRENT_LIST_LINE}) message(Arguments: ${ARGN}) message(Argument count: ${ARGC}) foreach(i RANGE 1 ${ARGC}) message( ARGV${i}: ${ARGV${i}}) endforeach() endmacro()对比总结宏 vs 函数选择场景推荐原因需要返回值函数使用 PARENT_SCOPE 清晰修改调用者变量宏直接修改无需特殊语法代码复用简单替换宏开销小效率高复杂逻辑函数作用域隔离易于调试生成代码块宏文本替换特性需要参数验证函数有独立作用域不易出错总结宏适合简单的文本替换和需要修改调用者变量的场景函数适合复杂的逻辑处理。优先考虑使用函数只有在需要宏的特殊特性时才使用宏。