Bazel:你如何获得生成文件的路径?
Bazel: How do you get the path to a generated file?
在 Bazel 中,给定构建目标,脚本(在 Bazel 之外 运行ning)如何获取生成文件的路径?
场景:我正在使用 Bazel 进行构建,然后在完成后,我想将结果复制到服务器。我只需要知道要复制哪些文件。我可以对文件列表进行硬编码,但我不想那样做。
一个简单的例子:这个 Bazel 脚本:
genrule(
name = "main",
srcs = ["main.in"],
outs = ["main.out"],
cmd = "cp $< $@",
)
如果您随后创建一个名为 main.in
的文件,然后创建 运行 bazel build :main
,bazel 报告:
INFO: Found 1 target...
Target //:main up-to-date:
bazel-genfiles/main.out
INFO: Elapsed time: 6.427s, Critical Path: 0.40s
所以有:bazel-genfiles/main.out
。但是我可以使用什么机器可读技术来获得该路径? (我可以解析 bazel build
的输出,但我们不鼓励这样做。)
我发现最接近的是使用 bazel query --output=xml :main
,它以 XML 格式转储有关 :main
的信息。输出包括这一行:
<rule-output name="//:main.out"/>
这太接近我想要的了。但是 name
是 Bazel 的标签格式;我不知道如何将其作为路径获取。
我可以在那个 name
字段上做一些字符串替换,把它变成 bazel-genfiles/main.out
;但即使那样也不可靠。如果我的 genrule
包含 output_to_bindir = 1
,那么输出将是 bazel-bin/main.out
。
此外,并非所有规则在 XML 输出中都有 <rule-output>
字段。例如,如果我的 BUILD
文件有此代码来制作 C 库:
cc_library(
name = "mylib",
srcs = glob(["*.c"])
)
bazel query --output=xml :mylib
的输出不包含 <rule-output>
或任何其他有用的内容:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<query version="2">
<rule class="cc_library" location="/Users/mikemorearty/src/bazel/test1/BUILD:8:1" name="//:mylib">
<string name="name" value="mylib"/>
<list name="srcs">
<label value="//:foo.c"/>
</list>
<rule-input name="//:foo.c"/>
<rule-input name="//tools/defaults:crosstool"/>
<rule-input name="@bazel_tools//tools/cpp:stl"/>
</rule>
</query>
在 bazel
的两个 运行 之间,输出路径应该相同。也就是说,如果您构建 //path/to:target
然后 bazel clean
并再次构建,它应该会生成相同的文件。由于此输出文件是常量,您可以 运行
ls "$(bazel info bazel-genfiles)/main.out"
我相信这会给你一个参考,一旦构建发生(它不会为你构建它)。
如果您希望从 target 转到将依赖于 rules_*
的文件名,您 运行ning。例如在他们的项目 rules_go, the output path depends on the arguments to the go_library
target. The rules_go team has recently documented this behavior 中。
一般来说,二进制输出路径在不同版本之间应该是稳定的,您可以相信它们不会有太大差异。但是,根据我的经验,这个问题通常表明您应该考虑将流程中以前的外部部分作为 genrule 或自定义规则移动到 Bazel 中。例如,我以前使用这个技巧来 assemble NPM 包,但现在我做了整个事情 in Bazel 并且有一个 target 生成 .tar我有兴趣上传到 NPM。也许你可以跟进一些关于你感兴趣的事情的细节,我们也许能够通过一个不依赖于外部系统理解 Bazel 构建路径的解决方案。
您可以使用bazel aquery
查询得到这些信息
动作图。
这是一个稍微丰富一点的例子,一个文件有两个输出文件
种族规则:
$ ls
BUILD main.in WORKSPACE
$ cat WORKSPACE
$ cat BUILD
genrule(
name = "main",
srcs = ["main.in"],
outs = ["main.o1", "main.o2"],
cmd = "cp $< $(location main.o1); cp $< $(location main.o2)",
)
$ cat main.in
hello
使用bazel aquery //:main --output=textproto
查询具有机器可读输出的动作图(原型为analysis.ActionGraphContainer
):
$ bazel aquery //:main --output=textproto >aquery_result 2>/dev/null
$ cat aquery_result
artifacts {
id: "0"
exec_path: "main.in"
}
artifacts {
id: "1"
exec_path: "external/bazel_tools/tools/genrule/genrule-setup.sh"
}
artifacts {
id: "2"
exec_path: "bazel-out/k8-fastbuild/genfiles/main.o1"
}
artifacts {
id: "3"
exec_path: "bazel-out/k8-fastbuild/genfiles/main.o2"
}
actions {
target_id: "0"
action_key: "dd7fd759bbecce118a399c6ce7b0c4aa"
mnemonic: "Genrule"
configuration_id: "0"
arguments: "/bin/bash"
arguments: "-c"
arguments: "source external/bazel_tools/tools/genrule/genrule-setup.sh; cp main.in bazel-out/k8-fastbuild/genfiles/main.o1; cp main.in bazel-out/k8-fastbuild/genfiles/main.o2"
input_dep_set_ids: "0"
output_ids: "2"
output_ids: "3"
}
targets {
id: "0"
label: "//:main"
rule_class_id: "0"
}
dep_set_of_files {
id: "0"
direct_artifact_ids: "0"
direct_artifact_ids: "1"
}
configuration {
id: "0"
mnemonic: "k8-fastbuild"
platform_name: "k8"
}
rule_classes {
id: "0"
name: "genrule"
}
数据并不完全集中在一处,但请注意:
- ID 为
2
和 3
的工件对应于我们想要的两个
输出文件,并将这些工件的输出位置列为
磁盘上相对于工作区根目录的文件路径;
- 目标 ID
0
的 artifacts
条目与工件相关联
ID 2
和 3
;和
- ID 为
"0"
的 targets
条目与 //:main
关联
标签。
鉴于这个简单的结构,我们可以轻松地将脚本组合在一起以
列出与提供的标签对应的所有输出文件。我找不到
直接依赖 Bazel 对 analysis.proto
或其定义的方法
来自外部存储库的语言绑定,因此您可以修补
以下脚本进入 bazelbuild/bazel
存储库本身:
tools/list_outputs/list_outputs.py
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Parse an `aquery` result to list outputs created for a target.
Use this binary in conjunction with `bazel aquery` to determine the
paths on disk to output files of a target.
Example usage: first, query the action graph for the target that you
want to analyze:
bazel aquery //path/to:target --output=textproto >/tmp/aquery_result
Then, from the Bazel repository:
bazel run //tools/list_outputs -- \
--aquery_result /tmp/aquery_result \
--label //path/to:target \
;
This will print a list of zero or more output files emitted by the given
target, like:
bazel-out/k8-fastbuild/foo.genfile
bazel-out/k8-fastbuild/bar.genfile
If the provided label does not appear in the output graph, an error will
be raised.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import sys
from absl import app
from absl import flags
from google.protobuf import text_format
from src.main.protobuf import analysis_pb2
flags.DEFINE_string(
"aquery_result",
None,
"Path to file containing result of `bazel aquery ... --output=textproto`.",
)
flags.DEFINE_string(
"label",
None,
"Label whose outputs to print.",
)
def die(message):
sys.stderr.write("fatal: %s\n" % (message,))
sys.exit(1)
def main(unused_argv):
if flags.FLAGS.aquery_result is None:
raise app.UsageError("Missing `--aquery_result` argument.")
if flags.FLAGS.label is None:
raise app.UsageError("Missing `--label` argument.")
if flags.FLAGS.aquery_result == "-":
aquery_result = sys.stdin.read()
else:
with open(flags.FLAGS.aquery_result) as infile:
aquery_result = infile.read()
label = flags.FLAGS.label
action_graph_container = analysis_pb2.ActionGraphContainer()
text_format.Merge(aquery_result, action_graph_container)
matching_targets = [
t for t in action_graph_container.targets
if t.label == label
]
if len(matching_targets) != 1:
die(
"expected exactly one target with label %r; found: %s"
% (label, sorted(t.label for t in matching_targets))
)
target = matching_targets[0]
all_artifact_ids = frozenset(
artifact_id
for action in action_graph_container.actions
if action.target_id == target.id
for artifact_id in action.output_ids
)
for artifact in action_graph_container.artifacts:
if artifact.id in all_artifact_ids:
print(artifact.exec_path)
if __name__ == "__main__":
app.run(main)
tools/list_outputs/BUILD
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package(default_visibility = ["//visibility:public"])
licenses(["notice"]) # Apache 2.0
filegroup(
name = "srcs",
srcs = glob(["**"]),
)
py_binary(
name = "list_outputs",
srcs = ["list_outputs.py"],
srcs_version = "PY2AND3",
deps = [
"//third_party/py/abseil",
"//src/main/protobuf:analysis_py_proto",
],
)
作为 Git 补丁,为方便起见:
https://gist.github.com/wchargin/5e6a43a203d6c95454aae2886c5b54e4
请注意,此代码尚未经过审查或验证
正确性;我提供它主要是作为示例。如果对你有用
你,然后也许这个周末我可以为它写一些测试并 PR
针对 Bazel 本身。
在 Bazel 中,给定构建目标,脚本(在 Bazel 之外 运行ning)如何获取生成文件的路径?
场景:我正在使用 Bazel 进行构建,然后在完成后,我想将结果复制到服务器。我只需要知道要复制哪些文件。我可以对文件列表进行硬编码,但我不想那样做。
一个简单的例子:这个 Bazel 脚本:
genrule(
name = "main",
srcs = ["main.in"],
outs = ["main.out"],
cmd = "cp $< $@",
)
如果您随后创建一个名为 main.in
的文件,然后创建 运行 bazel build :main
,bazel 报告:
INFO: Found 1 target...
Target //:main up-to-date:
bazel-genfiles/main.out
INFO: Elapsed time: 6.427s, Critical Path: 0.40s
所以有:bazel-genfiles/main.out
。但是我可以使用什么机器可读技术来获得该路径? (我可以解析 bazel build
的输出,但我们不鼓励这样做。)
我发现最接近的是使用 bazel query --output=xml :main
,它以 XML 格式转储有关 :main
的信息。输出包括这一行:
<rule-output name="//:main.out"/>
这太接近我想要的了。但是 name
是 Bazel 的标签格式;我不知道如何将其作为路径获取。
我可以在那个 name
字段上做一些字符串替换,把它变成 bazel-genfiles/main.out
;但即使那样也不可靠。如果我的 genrule
包含 output_to_bindir = 1
,那么输出将是 bazel-bin/main.out
。
此外,并非所有规则在 XML 输出中都有 <rule-output>
字段。例如,如果我的 BUILD
文件有此代码来制作 C 库:
cc_library(
name = "mylib",
srcs = glob(["*.c"])
)
bazel query --output=xml :mylib
的输出不包含 <rule-output>
或任何其他有用的内容:
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<query version="2">
<rule class="cc_library" location="/Users/mikemorearty/src/bazel/test1/BUILD:8:1" name="//:mylib">
<string name="name" value="mylib"/>
<list name="srcs">
<label value="//:foo.c"/>
</list>
<rule-input name="//:foo.c"/>
<rule-input name="//tools/defaults:crosstool"/>
<rule-input name="@bazel_tools//tools/cpp:stl"/>
</rule>
</query>
在 bazel
的两个 运行 之间,输出路径应该相同。也就是说,如果您构建 //path/to:target
然后 bazel clean
并再次构建,它应该会生成相同的文件。由于此输出文件是常量,您可以 运行
ls "$(bazel info bazel-genfiles)/main.out"
我相信这会给你一个参考,一旦构建发生(它不会为你构建它)。
如果您希望从 target 转到将依赖于 rules_*
的文件名,您 运行ning。例如在他们的项目 rules_go, the output path depends on the arguments to the go_library
target. The rules_go team has recently documented this behavior 中。
一般来说,二进制输出路径在不同版本之间应该是稳定的,您可以相信它们不会有太大差异。但是,根据我的经验,这个问题通常表明您应该考虑将流程中以前的外部部分作为 genrule 或自定义规则移动到 Bazel 中。例如,我以前使用这个技巧来 assemble NPM 包,但现在我做了整个事情 in Bazel 并且有一个 target 生成 .tar我有兴趣上传到 NPM。也许你可以跟进一些关于你感兴趣的事情的细节,我们也许能够通过一个不依赖于外部系统理解 Bazel 构建路径的解决方案。
您可以使用bazel aquery
查询得到这些信息
动作图。
这是一个稍微丰富一点的例子,一个文件有两个输出文件 种族规则:
$ ls
BUILD main.in WORKSPACE
$ cat WORKSPACE
$ cat BUILD
genrule(
name = "main",
srcs = ["main.in"],
outs = ["main.o1", "main.o2"],
cmd = "cp $< $(location main.o1); cp $< $(location main.o2)",
)
$ cat main.in
hello
使用bazel aquery //:main --output=textproto
查询具有机器可读输出的动作图(原型为analysis.ActionGraphContainer
):
$ bazel aquery //:main --output=textproto >aquery_result 2>/dev/null
$ cat aquery_result
artifacts {
id: "0"
exec_path: "main.in"
}
artifacts {
id: "1"
exec_path: "external/bazel_tools/tools/genrule/genrule-setup.sh"
}
artifacts {
id: "2"
exec_path: "bazel-out/k8-fastbuild/genfiles/main.o1"
}
artifacts {
id: "3"
exec_path: "bazel-out/k8-fastbuild/genfiles/main.o2"
}
actions {
target_id: "0"
action_key: "dd7fd759bbecce118a399c6ce7b0c4aa"
mnemonic: "Genrule"
configuration_id: "0"
arguments: "/bin/bash"
arguments: "-c"
arguments: "source external/bazel_tools/tools/genrule/genrule-setup.sh; cp main.in bazel-out/k8-fastbuild/genfiles/main.o1; cp main.in bazel-out/k8-fastbuild/genfiles/main.o2"
input_dep_set_ids: "0"
output_ids: "2"
output_ids: "3"
}
targets {
id: "0"
label: "//:main"
rule_class_id: "0"
}
dep_set_of_files {
id: "0"
direct_artifact_ids: "0"
direct_artifact_ids: "1"
}
configuration {
id: "0"
mnemonic: "k8-fastbuild"
platform_name: "k8"
}
rule_classes {
id: "0"
name: "genrule"
}
数据并不完全集中在一处,但请注意:
- ID 为
2
和3
的工件对应于我们想要的两个 输出文件,并将这些工件的输出位置列为 磁盘上相对于工作区根目录的文件路径; - 目标 ID
0
的artifacts
条目与工件相关联 ID2
和3
;和 - ID 为
"0"
的targets
条目与//:main
关联 标签。
鉴于这个简单的结构,我们可以轻松地将脚本组合在一起以
列出与提供的标签对应的所有输出文件。我找不到
直接依赖 Bazel 对 analysis.proto
或其定义的方法
来自外部存储库的语言绑定,因此您可以修补
以下脚本进入 bazelbuild/bazel
存储库本身:
tools/list_outputs/list_outputs.py
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""Parse an `aquery` result to list outputs created for a target.
Use this binary in conjunction with `bazel aquery` to determine the
paths on disk to output files of a target.
Example usage: first, query the action graph for the target that you
want to analyze:
bazel aquery //path/to:target --output=textproto >/tmp/aquery_result
Then, from the Bazel repository:
bazel run //tools/list_outputs -- \
--aquery_result /tmp/aquery_result \
--label //path/to:target \
;
This will print a list of zero or more output files emitted by the given
target, like:
bazel-out/k8-fastbuild/foo.genfile
bazel-out/k8-fastbuild/bar.genfile
If the provided label does not appear in the output graph, an error will
be raised.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import sys
from absl import app
from absl import flags
from google.protobuf import text_format
from src.main.protobuf import analysis_pb2
flags.DEFINE_string(
"aquery_result",
None,
"Path to file containing result of `bazel aquery ... --output=textproto`.",
)
flags.DEFINE_string(
"label",
None,
"Label whose outputs to print.",
)
def die(message):
sys.stderr.write("fatal: %s\n" % (message,))
sys.exit(1)
def main(unused_argv):
if flags.FLAGS.aquery_result is None:
raise app.UsageError("Missing `--aquery_result` argument.")
if flags.FLAGS.label is None:
raise app.UsageError("Missing `--label` argument.")
if flags.FLAGS.aquery_result == "-":
aquery_result = sys.stdin.read()
else:
with open(flags.FLAGS.aquery_result) as infile:
aquery_result = infile.read()
label = flags.FLAGS.label
action_graph_container = analysis_pb2.ActionGraphContainer()
text_format.Merge(aquery_result, action_graph_container)
matching_targets = [
t for t in action_graph_container.targets
if t.label == label
]
if len(matching_targets) != 1:
die(
"expected exactly one target with label %r; found: %s"
% (label, sorted(t.label for t in matching_targets))
)
target = matching_targets[0]
all_artifact_ids = frozenset(
artifact_id
for action in action_graph_container.actions
if action.target_id == target.id
for artifact_id in action.output_ids
)
for artifact in action_graph_container.artifacts:
if artifact.id in all_artifact_ids:
print(artifact.exec_path)
if __name__ == "__main__":
app.run(main)
tools/list_outputs/BUILD
# Copyright 2019 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package(default_visibility = ["//visibility:public"])
licenses(["notice"]) # Apache 2.0
filegroup(
name = "srcs",
srcs = glob(["**"]),
)
py_binary(
name = "list_outputs",
srcs = ["list_outputs.py"],
srcs_version = "PY2AND3",
deps = [
"//third_party/py/abseil",
"//src/main/protobuf:analysis_py_proto",
],
)
作为 Git 补丁,为方便起见: https://gist.github.com/wchargin/5e6a43a203d6c95454aae2886c5b54e4
请注意,此代码尚未经过审查或验证 正确性;我提供它主要是作为示例。如果对你有用 你,然后也许这个周末我可以为它写一些测试并 PR 针对 Bazel 本身。