如果 A
在构建时需要 B
,则目标 A
依赖于目标 B
;或
执行时间。“依赖”关系会引发
有向无环图
(DAG),称为依赖关系图。
目标的直接依赖项是指路径可以访问的其他目标 长度为 1 的映射关系。目标的传递依赖项包括 通过图表上任意长度的路径所依赖的目标。
事实上,在 build 上下文中,有两个依赖关系图: 实际依赖项和声明的依赖项图。大部分 这两个图表十分相似,无需进行区分, 它对于下面的讨论非常有用。
实际依赖项和声明的依赖项
目标 X
实际上依赖于目标 Y
(如果 Y
必须存在);
并保持最新状态,以确保正确构建 X
。Built 可能
生成、处理、编译、链接、归档、压缩、执行或
构建期间日常执行的任何其他类型的任务。
目标 X
声明有对目标 Y
的依赖项(如果存在依赖项)
X
软件包中从 X
到 Y
的边缘。
要实现正确的构建,实际依赖项 A 的图必须是
声明的依赖项的图表 D。也就是说,
A 中直接连接的节点 x --> y
也必须直接连接到
D。可以说,D 是 A 的超近似值。
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(); } |
<ph type="x-smartling-placeholder"> |
<ph type="x-smartling-placeholder"> |
声明的依赖项优先于实际依赖项。一切正常。
2. 添加未声明的依赖项
当有人向 a
添加代码以创建
对 c
的直接 actual 依赖项,但忘记在 build 文件中声明它
a/BUILD
。
a / a.in |
|
---|---|
import b; import c; b.foo(); c.garply(); |
|
<ph type="x-smartling-placeholder"> |
<ph type="x-smartling-placeholder"> |
声明的依赖项不再高估实际依赖项。
这样可能没问题,因为两个图的传递闭包相等,
但掩盖了问题:a
实际对 c
有未声明的依赖项。
3. 声明的依赖关系图与实际依赖关系图之间的偏差
当有人重构 b
以使其不再依赖于它时,就会发现危害
c
,无意中将 a
突破了 no
自己的过错。
b/BUILD |
|
---|---|
rule( name = "b", srcs = "b.in", deps = "//d:d", ) |
|
b / b.in |
|
import d; function foo() { d.baz(); } |
|
<ph type="x-smartling-placeholder"> |
<ph type="x-smartling-placeholder"> |
声明的依赖关系图现在是实际 包括以传递方式关闭的依赖项;构建可能会失败
通过确保
已在 BUILD
文件中正确声明第 2 步中引入的 a
到 c
。
依赖项类型
大多数构建规则都有三个属性,用于指定不同类型的
通用依赖项:srcs
、deps
和 data
。具体如下所述。对于
请参见
所有规则共有的属性。
许多规则还有适用于特定规则类型的
依赖项,例如 compiler
或 resources
。详见
构建百科全书。
srcs
个依赖项
输出源文件的规则所直接使用的文件。
deps
个依赖项
指向提供头文件的单独编译的模块的规则; 符号、库、数据等
data
个依赖项
build 目标可能需要一些数据文件才能正常运行。这些数据文件 不是源代码:它们不会影响目标的构建方式。例如, 单元测试可能会将函数的输出与文件的内容进行比较。当您 构建单元测试时,您不需要此文件,但在运行 测试。这同样适用于在执行期间启动的工具。
构建系统在一个隔离的目录中运行测试,该目录下仅列出了
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
标签作为数据依赖关系。
<ph type="x-smartling-placeholder"></ph> BUILD 文件 | <ph type="x-smartling-placeholder"></ph> 曝光度 |