使用Bazel构建依赖ITK库的C++二进制文件出现链接错误(未定义引用)
问题背景
我用Bazel管理一个项目,结构简化后如下:
./ ┗━ utils ┗━ BUILD ┗━ tool1 ┗━ BUILD ┗━ itk.BUILD ┗━ WORKSPACE
核心需求是构建依赖utils库的tool1二进制文件,utils本身依赖预编译在third_party/itk的ITK库。目前bazel build //utils:utils能成功构建,但bazel build //tool1时出现大量链接错误,仅剩libitkvnl_algo-4.13.a相关的报错,示例:
external/itk/lib/libitkvnl_algo-4.13.a(vnl_cholesky.cxx.o): In function `vnl_cholesky::vnl_cholesky(vnl_matrix<double> const&, vnl_cholesky::Operation)': vnl_cholesky.cxx:(.text+0x99): undefined reference to `v3p_netlib_dpofa_'
这些缺失的符号均来自ITK自身库,且对应的.a文件已经出现在Bazel生成的链接命令中,但单独调整单个库的顺序无效,需要调整多个库的链接顺序。
项目各配置文件内容如下:
utils/BUILD
cc_library( name = "utils", srcs = glob( [ "utils/*.cpp", "include/**", ], ), hdrs = glob([ "include/*.h", ]), copts = [ "-DITK_USE_SYSTEM_ZLIB", ], includes = ["./include"], linkopts = ["-ldl", "-lz", "-lm", "-lgomp", "-lpthread", "-lcrypto", "-lrt"], visibility = ["//visibility:public"], deps = [ "@itk//:itk_libs", ], )
tool1/BUILD
cc_binary( name = "tool1", srcs = ["tool1.cpp"], deps = [ "//utils:utils", ], )
WORKSPACE
new_local_repository( name = "itk", path = "third_party/itk", build_file = "itk.BUILD", )
itk.BUILD
cc_library( name = "itk_libs", srcs = glob(["lib/**/*.a"]), hdrs = glob([ "include/ITK-4.13/**/*.h", "include/ITK-4.13/*.h", "include/ITK-4.13/**/*.hxx", "include/ITK-4.13/*.hxx", "include/ITK-4.13/**/*.txx", "include/ITK-4.13/*.txx", ]), strip_include_prefix = "include/ITK-4.13", visibility = ["//visibility:public"], )
问题原因
当前itk.BUILD把所有ITK静态库打包成了一个cc_library,但Bazel无法识别这些静态库内部的依赖关系。utils构建成功只是因为它的代码没有触发对跨库引用符号的调用,而tool1的代码路径用到了这些符号,就暴露了链接顺序错误的问题——需要被引用的库(比如libitkv3p_netlib-4.13.a)必须放在引用它的库(libitkvnl_algo-4.13.a)之后链接,但Bazel当前的处理顺序不满足这个要求。
解决办法
1. 拆分ITK库,显式声明依赖(推荐方案)
不要把所有ITK库打包成一个集合,而是为每个.a文件单独创建cc_library,并明确写出它们之间的依赖关系。例如libitkvnl_algo-4.13.a依赖libitkvnl-4.13.a和libitkv3p_netlib-4.13.a,修改itk.BUILD如下:
# 定义最底层的依赖库 cc_library( name = "itkv3p_netlib", srcs = ["lib/libitkv3p_netlib-4.13.a"], hdrs = glob([ "include/ITK-4.13/**/*.h", "include/ITK-4.13/**/*.hxx", "include/ITK-4.13/**/*.txx", ]), strip_include_prefix = "include/ITK-4.13", visibility = ["//visibility:public"], ) cc_library( name = "itkvnl", srcs = ["lib/libitkvnl-4.13.a"], hdrs = glob([ "include/ITK-4.13/**/*.h", "include/ITK-4.13/**/*.hxx", "include/ITK-4.13/**/*.txx", ]), strip_include_prefix = "include/ITK-4.13", visibility = ["//visibility:public"], deps = [":itkv3p_netlib"], # 声明依赖关系 ) # 定义依赖上层的库 cc_library( name = "itkvnl_algo", srcs = ["lib/libitkvnl_algo-4.13.a"], hdrs = glob([ "include/ITK-4.13/**/*.h", "include/ITK-4.13/**/*.hxx", "include/ITK-4.13/**/*.txx", ]), strip_include_prefix = "include/ITK-4.13", visibility = ["//visibility:public"], deps = [":itkvnl", ":itkv3p_netlib"], # 声明依赖关系 ) # 创建集合库,方便其他模块引用 cc_library( name = "itk_libs", visibility = ["//visibility:public"], deps = [ ":itkvnl_algo", ":itkvnl", ":itkv3p_netlib", # 按依赖顺序添加其他需要的ITK库 ], )
这样Bazel会根据依赖关系自动调整链接顺序,确保被依赖的库后链接,解决符号未定义问题。
2. 强制指定链接顺序(临时方案)
如果拆分库的工作量太大,可以直接在tool1的cc_binary中通过linkopts手动指定ITK库的链接顺序,把引用其他库的静态库放在前面,被依赖的库放在后面:
cc_binary( name = "tool1", srcs = ["tool1.cpp"], deps = [ "//utils:utils", ], linkopts = [ "-Lexternal/itk/lib", "-litkvnl_algo-4.13", "-litkvnl-4.13", "-litkv3p_netlib-4.13", # 按正确顺序添加其他ITK库 ], )
这个方法需要明确所有库的依赖关系,维护成本较高,仅作为临时替代方案。
3. 检查预编译库的兼容性
确认预编译的ITK库与当前Bazel使用的编译器、编译选项(如C++标准、优化级别、宏定义)一致,避免因ABI不兼容导致符号无法识别。
验证
修改配置后执行bazel build //tool1,如果链接错误消失,则说明问题已解决;若仍有错误,检查是否遗漏了某些库的依赖声明。
内容的提问来源于stack exchange,提问作者xoreaxeax

