如何在Bazel中处理c++库循环依赖?

ffscu2ro  于 2023-05-30  发布在  其他
关注(0)|答案(3)|浏览(233)

Bazel是否有类似于gcc -(archives -)或--start-group archives --end-group link的功能来处理库之间的循环依赖?
或者,Bazel是否有一个功能,我们可以在Bazel中使用这个gcc功能?例如,使用诸如CCFLAGS之类的东西?

0pizxfdo

0pizxfdo1#

许多人认为最好首先避免循环依赖。
假设您有两个文件,其中a.cc #包含b.hb.cc #包含a.h

cc_library(
  name="a",
  srcs=["a.cc"],
  hdrs=["a.h"],
  deps=[":b"],  # a.cc #includes b.h
)

cc_library(
  name="b",
  srcs=["b.cc"],
  hdrs=["b.h"],
  deps=[":a"],  # b.cc #includes a.h
)

bazel将报告循环依赖。
如果你拥有所有有问题的代码,避免这些循环依赖的更直接的方法是重新整理你的源代码和bazel包,首先避免依赖。
这里的一个策略是将常见的东西重构为第三个“核心”库c,它既不依赖于a也不依赖于b

cc_library(
  name="a",
  srcs=["a.cc"],
  hdrs=["a.h"],
  deps=[":c"],  # a.cc #includes c.h
)

cc_library(
  name="b",
  srcs=["b.cc"],
  hdrs=["b.h"],
  deps=[":c"],  # b.cc #includes c.h
)

cc_library(
  name="c",
  srcs=["c.cc"],
  hdrs=["c.h"],
)

另一种策略是使用某种构建步骤将a.hb.h复制到某个“公共头文件”文件夹(该文件夹本身不依赖于任何其他文件)。这将删除文件级循环依赖,但从语义上讲,源代码在ab之间仍然存在循环依赖。

bq9c1y66

bq9c1y662#

如果您不拥有这些库,则必须等到实现cc_import-kind-of-rule。参见issue #818。我暂时还不知道有什么变通办法。

pn9klfpd

pn9klfpd3#

假设你有两个类A和B,如下所述,它们是有效的C++,并且你在设置构建规则时遇到了麻烦。您有几个选项可以修改构建规则或重构代码。
class_a.h

#ifndef class_a_h__
#define class_a_h__
// Forward declare class A
class A;

// Include dependencies here in case they require class A.
#include "class_b.h"

// Actually declare class A
class A {
 void SetB(B* b);
 // other fields and methods that may use b
};
#endif // class_a_h__

class_b.h

#ifndef class_b_h__
#define class_b_h__
// Forward declare class B
class B;

// Include dependencies here in case they require class B
#include "class_a.h"

// Actually declare class B
class B {
 public:
  void SetA(A* a);
  // other fields and methods that may use a
};
#endif // class_b_h__

选项一:声明libA和libB,但不声明它们的相互依赖性。

这里的神奇之处在于在依赖类lib的src中包含其他类的头文件。你可以让一个人依赖另一个人,但不能同时依赖两个人。

优点:

  • 无需重构源代码。
  • 重构BUILD文件所需的工作量最小,因此对于临时调试辅助工具非常有用。

缺点:

  • 如果some_other_lib不同时依赖于lib_a和lib_b,Bazel就不知道为什么链接器找不到丢失的符号。

BUILD.bzl

cc_library(name="lib_a",srcs=["class_a.cc", "class_b.h"],hdrs=["class_a.h"])
cc_library(name="lib_b",srcs=["class_b.cc", "class_a.h"],hdrs=["class_b.h"])
cc_library(name="some_other_lib",srcs=["other.cc"],hdrs=["other.h"],deps=[":lib_a",":lib_b"])

选项二:创建一个同时包含类A和类B的库。

我对这一点百感交集,但这可能是我默认的建议。由于循环依赖性,你不应该只使用一个库而不使用另一个库,所以我认为把它们组合成一个目标是可以的。

优点:

  • 准确捕获依赖关系图。
  • 无需重构源代码。

缺点:

  • 粒度更小的依赖图。你不能只依赖其中一个库。
  • 如果你的设置足够复杂,这可能会把一个有明显依赖关系的Makefile变成一个包含所有代码的cc_library。

BUILD.bzl

cc_library(name="lib_a_and_b",srcs=["class_a.cc", "class_b.cc"],hdrs=["class_a.h", "class_b.h"])
cc_library(name="some_other_library",srcs=["other.cc"],hdrs=["other.h"],deps=[":lib_a_and_b"])

选项三:提取基类。

这可以是一种选择,但对于任何现实世界的场景来说都是复杂的。你是否创建了以A和B为参数的类C,然后使用A和B,使用C,或者根据你需要它们如何工作的其他排列。我很确定这对于像事件总线这样知道事件处理程序的东西是不可能的,而处理程序需要知道总线来发送其他事件。

优点:

  • 可能会产生更好的代码。
  • 没有建立“魔法”。

缺点:

  • 不是小事。
  • 并不总是可能的。
  • 可能会导致更糟糕的代码。
  • 需要修改源代码。

选项3a:将A类和B类合并为一个类。

如果这是您的一个选项,那么它是一个更简单的重构选项。这些类显然已经相关。也许它们作为一个类更有意义。

优点:

  • 平凡重构
  • 没有构建“魔法”

缺点:

  • 并不总是可能的。
  • 可能会违反团队的编码原则。
  • 需要修改源代码。

相关问题