CMake跨目录生成文件引发静态库循环依赖构建失败问题
问题解答
可以让CMake先生成所有Protobuf生成文件,再执行编译阶段,这个需求完全可行。核心问题出在你当前的方案中,Protobuf生成动作绑定在各自的库目标上,而静态库的循环依赖导致CMake的依赖链断裂,无法触发前置的文件生成步骤。以下是具体的解决方案:
问题根源分析
你当前的配置里,每个库的Protobuf生成文件是作为库的源文件添加的,但因为foo和bar互相依赖,CMake在尝试构建其中一个库时,会跳过另一个库的生成步骤(循环依赖打破逻辑),导致编译时头文件缺失。
解决方案:独立Protobuf生成目标
将所有Protobuf文件的生成动作剥离为一个独立的顶层目标,让两个库都依赖这个目标,强制CMake先完成所有文件生成,再进入编译阶段。
1. 修改根目录CMakeLists.txt
引入Protobuf模块,并创建顶层生成目标:
cmake_minimum_required(VERSION 3.22) project(scratch CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") find_package(Protobuf REQUIRED) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) # 创建顶层目标,统一管理所有Protobuf生成动作 add_custom_target(generate_all_protos ALL) add_subdirectory(foo) add_subdirectory(bar)
2. 修改foo/CMakeLists.txt
将Protobuf生成动作绑定到顶层目标,并让库依赖该目标:
set(PROTO_SRC ${CMAKE_BINARY_DIR}/foo.pb.cc) set(PROTO_HDR ${CMAKE_BINARY_DIR}/foo.pb.h) add_custom_command( OUTPUT ${PROTO_SRC} ${PROTO_HDR} COMMAND protoc --cpp_out ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/foo.proto -I ${CMAKE_SOURCE_DIR} MAIN_DEPENDENCY foo.proto ) # 创建子生成目标,关联到顶层目标 add_custom_target(generate_foo_proto DEPENDS ${PROTO_SRC} ${PROTO_HDR}) add_dependencies(generate_all_protos generate_foo_proto) add_library(foo STATIC foo.cpp) # 让库依赖顶层生成目标,确保文件先生成 add_dependencies(foo generate_all_protos) target_sources(foo PUBLIC ${PROTO_SRC} ${PROTO_HDR}) # 处理静态库循环依赖,使用PUBLIC双向链接 target_link_libraries(foo PUBLIC bar ${Protobuf_LIBRARIES})
3. 修改bar/CMakeLists.txt
和foo的配置逻辑一致,替换对应名称即可:
set(PROTO_SRC ${CMAKE_BINARY_DIR}/bar.pb.cc) set(PROTO_HDR ${CMAKE_BINARY_DIR}/bar.pb.h) add_custom_command( OUTPUT ${PROTO_SRC} ${PROTO_HDR} COMMAND protoc --cpp_out ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/bar.proto -I ${CMAKE_SOURCE_DIR} MAIN_DEPENDENCY bar.proto ) add_custom_target(generate_bar_proto DEPENDS ${PROTO_SRC} ${PROTO_HDR}) add_dependencies(generate_all_protos generate_bar_proto) add_library(bar STATIC bar.cpp) add_dependencies(bar generate_all_protos) target_sources(bar PUBLIC ${PROTO_SRC} ${PROTO_HDR}) target_link_libraries(bar PUBLIC foo ${Protobuf_LIBRARIES})
简化方案:使用官方Protobuf模块
如果不想手动写add_custom_command,可以用CMake官方的protobuf_generate函数简化配置,效果一致:
# 在foo/CMakeLists.txt中替换生成部分 protobuf_generate( CPP_OUT ${CMAKE_BINARY_DIR} PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/foo.proto IMPORT_DIRS ${CMAKE_SOURCE_DIR} ) add_custom_target(generate_foo_proto DEPENDS ${CMAKE_BINARY_DIR}/foo.pb.cc ${CMAKE_BINARY_DIR}/foo.pb.h) add_dependencies(generate_all_protos generate_foo_proto)
关键说明
- 独立的生成目标
generate_all_protos会被CMake优先执行,确保所有.pb.h和.pb.cc文件都生成完毕后,才开始编译foo和bar库。 - 静态库的循环依赖需要通过
PUBLIC关键字双向链接,CMake会自动处理这种依赖关系的解析。
内容的提问来源于stack exchange,提问作者Steve Lorimer
相关产品推荐
相关产品推荐

