依赖项

如果目标 A 在构建或 执行时需要 B,则目标 A 依赖于 目标 B依赖于 关系会在目标上生成 有向无环图 (DAG),称为 依赖关系图

目标的 直接 依赖项是指在依赖关系图中通过长度为 1 的路径 可到达的其他目标。目标的 传递 依赖项是 指通过图中任意长度的路径依赖的目标。

实际上,在构建上下文中,存在两个依赖关系图: 实际依赖项图和 声明的依赖项图。大多数情况下,这两个图非常相似,因此无需进行这种区分,但 对于下面的讨论很有用。

实际依赖项和声明的依赖项

如果必须存在、 构建和更新目标 Y,才能正确构建 X,则目标 X 实际依赖于 目标 Y构建 可能 意味着生成、处理、编译、链接、归档、压缩、执行或 构建期间经常发生的任何其他类型的任务。

如果 X 的软件包中存在从 XY 的依赖项 边,则目标 X 具有对目标 Y声明的依赖项

为了正确构建,实际依赖项图 A 必须是 声明的依赖项图 D 的子图。也就是说, _A_ 中的每对 直接连接的节点 x --> y 也必须在 _D_ 中直接连接。可以说 DA过近似

BUILD 文件编写者必须向构建系统明确声明每个规则的所有实际直接 依赖项,并且不得声明其他依赖项。

如果不遵守此原则,会导致未定义的行为:构建可能会失败, 但更糟糕的是,构建可能依赖于某些先前的操作,或者依赖于传递 声明的依赖项,目标恰好具有。Bazel 会检查缺失的 依赖项并报告错误,但在所有情况下,这种检查都不可能 完成。

您无需(也不应)尝试列出间接导入的所有内容, 即使 需要 A 在执行时也是如此。

在构建目标 X 期间,构建工具会检查 X 的整个传递 闭包依赖项,以确保这些目标中的任何更改都 反映在最终结果中,并根据需要重建中间项。

依赖项的传递性会导致一个常见错误。有时, 一个文件中的代码可能会使用 间接 依赖项( 声明的依赖关系图中的传递边,但不是直接边)提供的代码。间接 依赖项不会显示在 BUILD 文件中。由于规则不 直接依赖于提供程序,因此无法跟踪更改,如 以下示例时间轴所示:

1. 声明的依赖项与实际依赖项匹配

起初,一切正常。软件包 a 中的代码使用软件包 b 中的代码。 软件包 b 中的代码使用软件包 c 中的代码,因此 a 传递 依赖于 c

a/BUILD b/BUILD
rule(
    name = "a",
    srcs = "a.in",
    deps = "//b:b",
)
      
rule(
    name = "b",
    srcs = "b.in",
    deps = "//c:c",
)
      
a / a.in b / b.in
import b;
b.foo();
    
import c;
function foo() {
  c.bar();
}
      
已声明的依赖关系图,箭头连接了 a、b 和 c
声明的 依赖关系图
与声明的依赖关系图相匹配的实际依赖关系图,其中箭头连接了 a、b 和 c
实际 依赖关系图

声明的依赖项过近似于实际依赖项。一切正常。

2. 添加未声明的依赖项

当有人向 a 添加代码以创建对 c 的 直接 实际 依赖项,但忘记在构建文件 a/BUILD 中声明该依赖项时,就会引入潜在的风险。

a / a.in  
        import b;
        import c;
        b.foo();
        c.garply();
      
 
已声明的依赖关系图,箭头连接了 a、b 和 c
声明的 依赖关系图
实际依赖关系图,其中箭头连接了 a、b 和 c。现在,箭头也连接了 A 和 C。这与声明的依赖关系图不匹配
实际 依赖关系图

声明的依赖项不再过近似于实际依赖项。 这可能会构建成功,因为这两个图的传递闭包相等, 但掩盖了一个问题:a 具有对 c 的实际依赖项,但未声明。

3. 声明的依赖关系图与实际依赖关系图之间的差异

当有人重构 b,使其不再依赖于 c 时,风险就会显现出来,从而无意中破坏了 a,而这并非他们自己的错。

  b/BUILD
 
rule(
    name = "b",
    srcs = "b.in",
    deps = "//d:d",
)
      
  b / b.in
 
      import d;
      function foo() {
        d.baz();
      }
      
已声明的依赖关系图,其中箭头连接了 a 和 b。
                  b 不再连接到 c,这会中断 a 与 c 之间的连接
声明的 依赖关系图
实际依赖关系图,显示了 a 连接到 b 和 c,但 b 不再连接到 c
实际 依赖关系图

现在,即使在传递闭包的情况下,声明的依赖关系图也是实际 依赖项的欠近似,构建很可能会失败。

通过确保在 BUILD 文件中正确声明第 2 步中引入的从 ac 的实际依赖项,可以避免此问题。

依赖项类型

大多数构建规则都有三个属性,用于指定不同类型的 通用依赖项:srcsdepsdata。具体说明如下。如需了解详情,请参阅 所有规则通用的属性

许多规则还具有其他属性,用于指定规则特定的 依赖项类型,例如 compilerresources。如需了解详情,请参阅 构建百科全书

srcs 依赖项

规则直接使用的文件或输出源文件的规则。

deps 依赖项

指向单独编译的模块的规则,这些模块提供头文件、 符号、库、数据等。

data 依赖项

构建目标可能需要一些数据文件才能正确运行。这些数据文件 不是源代码:它们不会影响目标的构建方式。例如,a 单元测试可能会将函数的输出与文件的内容进行比较。构建单元测试时,您不需要该文件,但在运行测试时需要该文件。在执行期间启动的工具也是如此。

构建系统在隔离的目录中运行测试,其中只有列为 data的文件可用。因此,如果二进制文件/库/测试需要某些文件才能运行, 请在 data 中指定这些文件(或包含这些文件的构建规则)。例如:

# I need a config file from a directory named env:
java_binary(
    name = "setenv",
    ...
    data = [":env/default_env.txt"],
)

# I need test data from another directory
sh_test(
    name = "regtest",
    srcs = ["regtest.sh"],
    data = [
        "//data:file1.txt",
        "//data:file2.txt",
        ...
    ],
)

这些文件可以使用相对路径 path/to/data/file。在测试中, 您可以通过将测试的源 目录的路径与工作区相对路径连接起来来引用这些文件,例如, ${TEST_SRCDIR}/workspace/path/to/data/file

使用标签引用目录

在查看我们的 BUILD 文件时,您可能会注意到一些 data 标签 引用了目录。这些标签以 /./ 结尾,如以下示例所示, 您不应使用这些标签:

不推荐data = ["//data/regression:unittest/."]

不推荐data = ["testdata/."]

不推荐data = ["testdata/"]

这看起来很方便,尤其是对于测试,因为它允许测试使用目录中的所有数据文件。

但请尽量不要这样做。为了确保在更改后正确进行增量重建(并 重新执行测试),构建系统必须知道 构建(或测试)的完整输入文件集。当您指定 目录时,构建系统仅在该目录本身 发生更改(由于添加或删除文件)时执行重建,但无法检测 对单个文件的修改,因为这些更改不会影响封闭目录。 您不应将目录指定为构建系统的输入,而应 枚举其中包含的文件集,无论是显式枚举还是使用 glob() 函数。(使用 ** 强制 glob() 递归。)

推荐data = glob(["testdata/**"])

遗憾的是,在某些情况下,必须使用目录标签。 例如,如果 testdata 目录包含名称不符合 标签语法 的文件,则显式枚举文件或使用 glob() 函数会生成无效标签错误。在这种情况下,您必须使用目录标签,但请注意 上述与不正确的重建相关的风险。

如果您必须使用目录标签,请注意,您无法使用相对 ../ 路径引用 父软件包;而应使用绝对路径,例如 //data/regression:unittest/.

data

任何需要使用多个文件的外部规则(例如测试)都必须 明确声明其对所有文件的依赖项。您可以使用 filegroup()BUILD 文件中将文件 分组在一起:

filegroup(
        name = 'my_data',
        srcs = glob(['my_unittest_data/*'])
)

然后,您可以在测试中将标签 my_data 引用为数据依赖项。

BUILD 文件 可见性