如何在CMake中当目标属性未设置时终止构建?
我在嵌入式开发中用CMake定义了一个对象库sdk,部分源文件/头文件需要根据硬件设备选择,因此写了set_target_device函数来设置设备相关内容,并为sdk目标添加target_device属性:
# SDK目录下的CMakeLists.txt add_library(sdk OBJECT) # 添加通用包含目录和源文件 target_include_directories(sdk PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc) target_sources(sdk PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/common.c) function(set_target_device) set(oneValueArgs TARGET DEVICE) cmake_parse_arguments(PARSE_ARGV 0 ARG "" "${oneValueArgs}" "") # 省略设备有效性检查 set_target_properties(sdk PROPERTIES target_device ${ARG_DEVICE}) # 省略根据设备添加源文件/头文件的逻辑 endfunction()
使用该库时需要添加子目录、调用函数并链接:
# 业务代码的CMakeLists.txt add_subdirectory(${CMAKE_SOURCE_DIR}/deps/sdk) set_target_device(TARGET firmware DEVICE target_device_value) target_link_libraries(firmware sdk)
现在的问题是:即使不调用set_target_device,构建仍能成功,但此时sdk缺少设备相关的文件,结果不可用。我需要实现不调用函数就终止构建的效果,试过自定义目标依赖、把目标放入函数等方式,都不太合理,想知道可行的解决方案。
方案一:配置阶段检查目标属性(改动最小)
在SDK的CMakeLists.txt末尾添加属性检查逻辑,在CMake配置阶段就判断target_device是否已设置,未设置则直接终止配置:
# SDK目录下的CMakeLists.txt末尾添加 get_target_property(SDK_TARGET_DEVICE sdk target_device) if(NOT SDK_TARGET_DEVICE) message(FATAL_ERROR "错误:必须调用set_target_device函数为sdk指定目标设备!") endif()
原理:CMake配置阶段会按顺序执行所有脚本,当用户未调用set_target_device时,sdk的target_device属性为空,触发FATAL_ERROR直接终止配置过程,后续构建不会执行。
方案二:延迟创建对象库(最强制)
把sdk对象库的创建逻辑完全移入set_target_device函数,未调用函数时sdk目标根本不存在,链接时直接报错:
# SDK目录下的CMakeLists.txt,移除原有的add_library function(set_target_device) set(oneValueArgs TARGET DEVICE) cmake_parse_arguments(PARSE_ARGV 0 ARG "" "${oneValueArgs}" "") # 先检查设备参数有效性 if(NOT ARG_DEVICE) message(FATAL_ERROR "错误:set_target_device必须指定DEVICE参数!") endif() # 在此创建对象库 add_library(sdk OBJECT) set_target_properties(sdk PROPERTIES target_device ${ARG_DEVICE}) # 添加通用包含目录和源文件 target_include_directories(sdk PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/inc) target_sources(sdk PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/common.c) # 根据设备添加特定文件 if(ARG_DEVICE STREQUAL "device_a") target_sources(sdk PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/device_a.c) target_include_directories(sdk PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc/device_a) elseif(ARG_DEVICE STREQUAL "device_b") target_sources(sdk PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/device_b.c) target_include_directories(sdk PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/inc/device_b) else() message(FATAL_ERROR "错误:不支持的目标设备${ARG_DEVICE}") endif() endfunction()
原理:用户必须调用set_target_device才能创建sdk目标,否则target_link_libraries会提示找不到sdk目标,从根源上强制用户执行配置步骤。
方案三:前置变量传递(简单直接)
要求用户在add_subdirectory之前设置全局变量SDK_TARGET_DEVICE,SDK侧检查该变量是否存在:
# 业务代码的CMakeLists.txt set(SDK_TARGET_DEVICE "target_device_value") add_subdirectory(${CMAKE_SOURCE_DIR}/deps/sdk) target_link_libraries(firmware sdk)
# SDK目录下的CMakeLists.txt if(NOT DEFINED SDK_TARGET_DEVICE) message(FATAL_ERROR "错误:必须在add_subdirectory之前设置SDK_TARGET_DEVICE变量指定目标设备!") endif() add_library(sdk OBJECT) set_target_properties(sdk PROPERTIES target_device ${SDK_TARGET_DEVICE}) # 添加通用和设备相关的文件逻辑...
原理:利用CMake变量的作用域,在导入SDK前强制用户传递设备参数,适合不需要复杂参数传递的场景。
内容的提问来源于stack exchange,提问作者DreyFax

