CMake:如何让目录B的配置依赖目录A的构建或查找结果
CMake 外部项目构建结果的条件配置问题
问题概述
我的场景本质是CMake可选依赖问题,但因使用ExternalProject_Add而更复杂:
- 外部项目目录“A”的CMakeLists.txt逻辑:
- 先执行
find_package(A-PKG ...),若A-PKG_FOUND为真,提取路径并定义相关变量; - 若未找到,通过
ExternalProject_Add(A-PRJ ...)下载、构建并安装该项目,再根据构建结果定义变量。
- 先执行
- 需求:目录“B”需根据“A”的查找或构建结果调整配置——具体是根据“A”的构建产出,设置
target_compile_definitions和target_include_directories。
核心矛盾
CMake的依赖仅控制构建顺序,不影响配置顺序:所有配置步骤均在构建前完成,导致“B”配置时“A”尚未生成产物,等“A”构建完成后,“B”的配置已固化,无法适配新的产物信息。
痛点需求
不想让“B”强制依赖“A”的成功构建/查找,希望实现无侵入逻辑:若“A”构建失败或未被包含,无需手动修改“B”(及“C”“D”等依赖项)的配置,“B”仍能正常构建,仅调整构建方式。
已尝试无效方案
- 为
ExternalProject_Add(A-PRJ ...)设置BUILD_BYPRODUCTS、STEP_TARGETS为build/install;添加add_custom_target(A-TARG ALL DEPENDS A-PRJ-build ...);多种依赖设置均仅能控制构建顺序,无法让“B”的配置依赖“A”的构建。 - 设置
A_FOUND缓存变量,但该变量在“A”配置阶段就被赋值,此时实际产物尚未生成,导致“B”尝试包含不存在的头文件而构建失败;若给“B”添加硬依赖“A”的构建,虽能保证头文件存在,但违反了“B”无需依赖“A”成功的需求。
临时 workaround
目前只能在首次配置并构建后,修改“B”的CMakeLists.txt触发重配置,再重新构建,效率低下。
可行解决方案
方案1:通过configure_file生成动态配置文件
- 在“A”的CMakeLists.txt末尾,统一生成包含配置信息的CMake脚本,记录
A_FOUND、A_INCLUDE_DIRS、A_DEFINITIONS等变量:if(A-PKG_FOUND) set(A_FOUND ON) set(A_INCLUDE_DIRS "${A-PKG_INCLUDE_DIRS}") set(A_DEFINITIONS "${A-PKG_DEFINITIONS}") else() set(A_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/A-install") set(A_FOUND ON) set(A_INCLUDE_DIRS "${A_INSTALL_DIR}/include") set(A_DEFINITIONS "-DUSE_BUILT_A=1") endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/A_config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/A_config.cmake" @ONLY ) - 在“B”的CMakeLists.txt中,先加载初始配置,再添加自定义命令依赖“A”的安装目标,确保配置文件更新后再应用到“B”的目标:
# 初始加载A的配置(可能为空) if(EXISTS "${A_BINARY_DIR}/A_config.cmake") include("${A_BINARY_DIR}/A_config.cmake") else() set(A_FOUND OFF) endif() # 生成B的配置头文件模板 configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/B_config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/B_config.h.tmp" @ONLY ) # 自定义命令:等待A构建完成后同步配置 add_custom_command( OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/B_config.h" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_BINARY_DIR}/B_config.h.tmp" "${CMAKE_CURRENT_BINARY_DIR}/B_config.h" DEPENDS "${A_BINARY_DIR}/A_config.cmake" A-PRJ-install ) # 绑定配置文件到B的目标 add_executable(B main.cpp) target_include_directories(B PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") if(A_FOUND) target_include_directories(B PRIVATE "${A_INCLUDE_DIRS}") target_compile_definitions(B PRIVATE "${A_DEFINITIONS}") endif() add_dependencies(B "${CMAKE_CURRENT_BINARY_DIR}/B_config.h")
方案2:在“A”的安装阶段触发全局重配置
在“A”的ExternalProject_Add的INSTALL_COMMAND中添加触发CMake重配置的命令,让“A”构建完成后自动重新运行配置阶段,此时“B”就能获取到最新的产物信息:
ExternalProject_Add(A-PRJ # 其他参数(如SOURCE_DIR、BINARY_DIR等)... INSTALL_COMMAND ${CMAKE_MAKE_PROGRAM} install COMMAND ${CMAKE_COMMAND} "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" )
注意:该方式会增加配置阶段的运行次数,可能延长总构建时间,但能确保配置的准确性。
方案3:通过自定义脚本延迟查找“A”的产物
如果“A”安装后会生成CMake包配置文件(如A-PKGConfig.cmake),可以通过自定义脚本在“A”安装完成后再执行find_package,将结果写入文件供“B”读取:
- 编写
find_A.cmake脚本:set(A_INSTALL_DIR "${CMAKE_ARGV3}") find_package(A-PKG PATHS "${A_INSTALL_DIR}" NO_DEFAULT_PATH) if(A-PKG_FOUND) file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/A_FOUND.txt" "ON") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/A_INCLUDES.txt" "${A-PKG_INCLUDE_DIRS}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/A_DEFS.txt" "${A-PKG_DEFINITIONS}") else() file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/A_FOUND.txt" "OFF") endif() - 在“B”的CMakeLists.txt中使用:
set(A_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/../A/A-install") add_custom_target(WaitForA COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/find_A.cmake" "${A_INSTALL_DIR}" DEPENDS A-PRJ-install ) add_executable(B main.cpp) add_dependencies(B WaitForA) # 读取文件内容并应用到目标配置 target_compile_definitions(B PRIVATE $<$<FILE_EXISTS:${CMAKE_CURRENT_BINARY_DIR}/A_DEFS.txt>:$(shell cat ${CMAKE_CURRENT_BINARY_DIR}/A_DEFS.txt)>) target_include_directories(B PRIVATE $<$<FILE_EXISTS:${CMAKE_CURRENT_BINARY_DIR}/A_INCLUDES.txt>:$(shell cat ${CMAKE_CURRENT_BINARY_DIR}/A_INCLUDES.txt)>)
内容的提问来源于stack exchange,提问作者Theodore Hall
相关产品推荐
相关产品推荐

