依赖项

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。
报告问题 查看源代码

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

目标的直接依赖项是指可通过依赖关系图中长度为 1 的路径访问的其他目标。目标的传递依赖项是指它通过图中长度不限的路径所依赖的目标。

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

实际和声明的依赖项

目标 X 实际上依赖于目标 Y,如果必须存在、构建并更新 Y,才能正确构建 X已构建是指在构建期间经常发生、生成、处理、编译、关联、归档、压缩、执行或执行的任何其他其他类型的任务。

如果 X 软件包中存在从 XY 的依赖项,则目标 X 会对目标 Y 进行声明的依赖项

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

BUILD 文件写入器必须向构建系统明确声明每条规则的所有实际直接依赖项,而不能再声明。

未能遵循此原则会导致出现未定义的行为:build 可能会失败,但更糟糕的是,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 的直接实际依赖项,但忘记在 build 文件 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 的连接
声明的依赖关系图
显示连接到 b 和 c,但 b 不再连接到 c 的实际依赖项图
实际依赖项图

声明的依赖关系图现在是实际依赖项的近似值,即使以传递方式关闭也是如此;构建可能会失败。

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

依赖项类型

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

许多规则还会针对规则特有的依赖项提供额外的特性,例如 compilerresources。如需了解详情,请参阅构建百科全书

srcs 依赖项

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

deps 依赖项

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

data 依赖项

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

构建系统在隔离的目录中运行测试,该目录中只有列为 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/.

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

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

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

构建文件 公开范围