cquery
是 query
的变体,可正确处理 select()
和 build 选项对 build 图的影响。
它通过运行 Bazel 分析阶段的结果来实现这一点,该阶段会集成这些效果。与之相反,query
会在评估选项之前运行 Bazel 加载阶段的结果。
例如:
$ cat > tree/BUILD <<EOF sh_library( name = "ash", deps = select({ ":excelsior": [":manna-ash"], ":americana": [":white-ash"], "//conditions:default": [":common-ash"], }), ) sh_library(name = "manna-ash") sh_library(name = "white-ash") sh_library(name = "common-ash") config_setting( name = "excelsior", values = {"define": "species=excelsior"}, ) config_setting( name = "americana", values = {"define": "species=americana"}, ) EOF
# Traditional query: query doesn't know which select() branch you will choose, # so it conservatively lists all of possible choices, including all used config_settings. $ bazel query "deps(//tree:ash)" --noimplicit_deps //tree:americana //tree:ash //tree:common-ash //tree:excelsior //tree:manna-ash //tree:white-ash # cquery: cquery lets you set build options at the command line and chooses # the exact dependencies that implies (and also the config_setting targets). $ bazel cquery "deps(//tree:ash)" --define species=excelsior --noimplicit_deps //tree:ash (9f87702) //tree:manna-ash (9f87702) //tree:americana (9f87702) //tree:excelsior (9f87702)
每个结果都包含目标平台的构建配置的唯一标识符 (9f87702)
。
由于 cquery
在配置的目标图上运行,因此它无法深入了解 build 操作等工件,也无法访问 test_suite
规则,因为它们不是配置的目标。对于前者,请参阅 aquery
。
基本语法
一个简单的 cquery
调用如下所示:
bazel cquery "function(//target)"
查询表达式 "function(//target)"
由以下内容构成:
function(...)
是在目标上运行的函数。cquery
支持query
的大多数函数,以及一些新函数。//target
是馈送给函数的表达式。在此示例中,表达式是一个简单的目标。不过,查询语言还允许嵌套函数。如需查看示例,请参阅查询指南。
cquery
要求目标完成加载和分析阶段。除非另有说明,否则 cquery
会解析查询表达式中列出的目标。如需查询顶级 build 目标的依赖项,请参阅 --universe_scope
。
配置
以下代码行:
//tree:ash (9f87702)
表示 //tree:ash
是在 ID 为 9f87702
的配置中构建的。对于大多数目标而言,这是定义配置的构建选项值的不透明哈希值。
如需查看配置的完整内容,请运行以下命令:
$ bazel config 9f87702
9f87702
是完整 ID 的前缀。这是因为完整的 ID 是 SHA-256 哈希,这些哈希较长且难以遵循。cquery
可以识别完整 ID 的任何有效前缀,类似于 Git 短哈希。如需查看完整 ID,请运行 $ bazel config
。
目标模式评估
//foo
在 cquery
和 query
中的含义不同。这是因为 cquery
会评估已配置的目标,而 build 图表可能包含多个已配置的 //foo
版本。
对于 cquery
,查询表达式中的目标模式会针对具有与该模式匹配的标签的每个已配置目标进行评估。输出是确定性的,但 cquery
除了核心查询排序协定之外,不对排序做出任何保证。
与使用 query
相比,这种方法生成的查询表达式结果更细微。例如,以下查询可能会产生多个结果:
# Analyzes //foo in the target configuration, but also analyzes # //genrule_with_foo_as_tool which depends on an exec-configured # //foo. So there are two configured target instances of //foo in # the build graph. $ bazel cquery //foo --universe_scope=//foo,//genrule_with_foo_as_tool //foo (9f87702) //foo (exec)
如果要精确声明要查询的实例,请使用 config
函数。
如需详细了解目标模式,请参阅 query
的目标模式文档。
函数
在 query
支持的一组函数中,cquery
支持其中的所有函数,但不支持 allrdeps
、buildfiles
、rbuildfiles
、siblings
、tests
和 visible
。
cquery
还引入了以下新函数:
config
expr ::= config(expr, word)
config
运算符尝试为由第一个参数表示的标签查找已配置的目标,以及由第二个参数指定的配置。
第二个参数的有效值为 null
或自定义配置哈希。哈希值可以从 $
bazel config
或之前的 cquery
的输出中检索。
示例:
$ bazel cquery "config(//bar, 3732cc8)" --universe_scope=//foo
$ bazel cquery "deps(//foo)" //bar (exec) //baz (exec) $ bazel cquery "config(//baz, 3732cc8)"
如果无法在指定配置中找到第一个参数的所有结果,则仅返回能够找到的结果。如果在指定配置中找不到结果,查询会失败。
选项
build 选项
cquery
在常规 Bazel build 上运行,因此会继承 build 期间可用的一组选项。
使用 cquery 选项
--universe_scope
(逗号分隔列表)
通常,配置的目标的依赖项会经历转换,这会导致其配置与其依赖项不同。借助此标志,您可以像对其他目标的依赖项或传递依赖项进行查询一样查询目标。例如:
# x/BUILD genrule( name = "my_gen", srcs = ["x.in"], outs = ["x.cc"], cmd = "$(locations :tool) $< >$@", tools = [":tool"], ) cc_binary( name = "tool", srcs = ["tool.cpp"], )
Genrules 会在执行配置中配置其工具,因此以下查询将生成以下输出:
查询 | 目标构建 | 输出 |
---|---|---|
bazel cquery "//x:tool" | //x:tool | //x:tool(targetconfig) |
bazel cquery "//x:tool" --universe_scope="//x:my_gen" | //x:my_gen | //x:tool(execconfig) |
如果设置了此标志,则会构建其内容。如果未设置,系统会改为构建查询表达式中提及的所有目标。构建的目标的传递闭包用作查询的宇宙。无论采用哪种方式,要构建的目标都必须在顶级可构建(即与顶级选项兼容)。cquery
会返回这些顶级目标的传递闭包中的结果。
即使可以在顶级查询表达式中构建所有目标,这样做也可能有好处。例如,明确设置 --universe_scope
可以防止在您不关心的配置中多次构建目标。此外,这还有助于指定您要查找的目标的配置版本(因为目前无法以任何其他方式完全指定该版本)。如果您的查询表达式比 deps(//foo)
更复杂,您应设置此标志。
--implicit_deps
(布尔值,default=True)
将此标志设置为 false 会过滤掉未在 BUILD 文件中明确设置,而是在其他位置由 Bazel 设置的所有结果。这包括过滤已解析的工具链。
--tool_deps
(布尔值,默认值为 True)
将此标志设为 false 会滤除所有配置的目标,其中从查询的目标到这些目标的路径会跨越目标配置和非目标配置之间的转换。如果查询的目标位于目标配置中,则设置 --notool_deps
将仅返回同时位于目标配置中的目标。如果查询的目标处于非目标配置中,设置 --notool_deps
只会返回也处于非目标配置中的目标。此设置通常不会影响过滤已解析的工具链。
--include_aspects
(布尔值,默认值为 True)
添加切面添加的依赖项。
如果此标志处于停用状态,则如果 X 仅通过某个方面依赖于 Y,cquery somepath(X, Y)
和 cquery deps(X) | grep 'Y'
会省略 Y。
输出格式
默认情况下,cquery 会以依赖项顺序输出标签和配置对列表。您还可以通过其他方式显示结果。
转场效果
--transitions=lite --transitions=full
配置转换用于在与顶级目标不同的配置中构建顶级目标下方的目标。
例如,目标可能会对其 tools
属性中的所有依赖项强制执行向 exec 配置的转换。这称为属性转换。规则还可以对自己的配置强制执行转换,称为规则类转换。此输出格式会输出与这些转换相关的信息,例如它们的类型以及对 build 选项的影响。
此输出格式由 --transitions
标志触发,该标志默认设置为 NONE
。可以设置为 FULL
或 LITE
模式。FULL
模式会输出有关规则类转换和属性转换的信息,包括转换前后选项的详细差异。LITE
模式会输出相同的信息,但不包含选项差异。
协议消息输出
--output=proto
此选项会导致以二进制协议缓冲区形式输出生成的目标。协议缓冲区的定义可在 src/main/protobuf/analysis_v2.proto 中找到。
CqueryResult
是包含 cquery 结果的顶级消息。它包含 ConfiguredTarget
消息列表和 Configuration
消息列表。每个 ConfiguredTarget
都有一个 configuration_id
,其值等于相应 Configuration
消息中 id
字段的值。
--[no]proto:include_configurations
默认情况下,cquery 结果会在每个配置的目标中返回配置信息。如果您想忽略此信息并获取格式与查询的 proto 输出完全相同的 proto 输出,请将此标志设置为 false。
如需了解更多与 proto 输出相关的选项,请参阅查询的 proto 输出文档。
图表输出
--output=graph
此选项会将输出生成为与 Graphviz 兼容的 .dot 文件。如需了解详情,请参阅 query
的图输出文档。cquery
还支持 --graph:node_limit
和 --graph:factored
。
输出文件
--output=files
此选项会输出查询匹配的每个目标生成的输出文件的列表,类似于 bazel build
调用结束时输出的列表。输出仅包含在请求的输出组中宣传的文件(由 --output_groups
标志确定)。它包含源文件。
此输出格式发出的所有路径都相对于 execroot(可通过 bazel info execution_root
获取)。如果存在 bazel-out
便捷符号链接,则主仓库中文件的路径也会相对于工作区目录解析。
使用 Starlark 定义输出格式
--output=starlark
此输出格式会针对查询结果中的每个配置目标调用 Starlark 函数,并输出调用返回的值。--starlark:file
标志指定 Starlark 文件的位置,该文件定义了一个名为 format
且具有单个形参 target
的函数。系统会针对查询结果中的每个 Target 调用此函数。或者,为方便起见,您也可以使用 --starlark:expr
标志仅指定声明为 def format(target): return expr
的函数正文。
“cquery”Starlark 方言
cquery Starlark 环境不同于 BUILD 或 .bzl 文件。它包含所有核心 Starlark 内置常量和函数,以及下面介绍的几个 cquery 专用函数,但不包含 glob
、native
或 rule
等函数,也不支持加载语句。
build_options(target)
build_options(target)
会返回一个映射,其键是 build 选项标识符(请参阅配置),值是其 Starlark 值。值不是有效 Starlark 值的 build 选项会从此映射中省略。
如果目标是输入文件,build_options(target)
会返回 None,因为输入文件目标的配置是 null。
providers(target)
providers(target)
会返回一个映射,其中键是提供程序的名称(例如 "DefaultInfo"
),值是其 Starlark 值。此映射中省略了值不是有效 Starlark 值的提供程序。
示例
输出由 //foo
生成的所有文件的基本名称列表(以空格分隔):
bazel cquery //foo --output=starlark \ --starlark:expr="' '.join([f.basename for f in target.files.to_list()])"
输出 //bar
及其子软件包中 rule 目标生成的所有文件的路径(以空格分隔)的列表:
bazel cquery 'kind(rule, //bar/...)' --output=starlark \ --starlark:expr="' '.join([f.path for f in target.files.to_list()])"
输出 //foo
注册的所有操作的缩写列表。
bazel cquery //foo --output=starlark \ --starlark:expr="[a.mnemonic for a in target.actions]"
输出由 cc_library
//baz
注册的编译输出列表。
bazel cquery //baz --output=starlark \ --starlark:expr="[f.path for f in target.output_groups.compilation_outputs.to_list()]"
在构建 //foo
时,输出命令行选项 --javacopt
的值。
bazel cquery //foo --output=starlark \ --starlark:expr="build_options(target)['//command_line_option:javacopt']"
输出每个目标的标签,且每个目标只有一个输出。此示例使用文件中定义的 Starlark 函数。
$ cat example.cquery def has_one_output(target): return len(target.files.to_list()) == 1 def format(target): if has_one_output(target): return target.label else: return "" $ bazel cquery //baz --output=starlark --starlark:file=example.cquery
输出每个完全使用 Python 3 的目标的标签。此示例使用文件中定义的 Starlark 函数。
$ cat example.cquery def format(target): p = providers(target) py_info = p.get("PyInfo") if py_info and py_info.has_py3_only_sources: return target.label else: return "" $ bazel cquery //baz --output=starlark --starlark:file=example.cquery
从用户定义的提供程序中提取值。
$ cat some_package/my_rule.bzl MyRuleInfo = provider(fields={"color": "the name of a color"}) def _my_rule_impl(ctx): ... return [MyRuleInfo(color="red")] my_rule = rule( implementation = _my_rule_impl, attrs = {...}, ) $ cat example.cquery def format(target): p = providers(target) my_rule_info = p.get("//some_package:my_rule.bzl%MyRuleInfo'") if my_rule_info: return my_rule_info.color return "" $ bazel cquery //baz --output=starlark --starlark:file=example.cquery
cquery 与 query
cquery
和 query
互为补充,在不同的细分领域都表现出色。请考虑以下事项,以确定哪个选项适合您:
cquery
会跟踪特定的select()
分支,以模拟您构建的确切图表。query
不知道 build 会选择哪个分支,因此会通过包含所有分支来进行过度近似。- 与
query
相比,cquery
的准确性需要构建更多图表。具体而言,cquery
会评估已配置的目标,而query
仅会评估目标。这将需要更长的时间,并且会占用更多内存。 cquery
对查询语言的解读会引入query
避免的模棱两可。例如,如果"//foo"
存在于两个配置中,cquery "deps(//foo)"
应使用哪个?config
函数可以帮助您解决此问题。cquery
是一款较新的工具,不支持某些用例。如需了解详情,请参阅已知问题。
已知问题
cquery
“构建”的所有目标都必须具有相同的配置。
在评估查询之前,cquery
会触发构建,直到构建操作执行之前。它“构建”的目标默认从查询表达式中显示的所有标签中选择(可以使用 --universe_scope
替换此设置)。这些标签必须具有相同的配置。
虽然这些规则通常共享顶级“目标”配置,但规则可以通过传入边缘转换更改自己的配置。这正是 cquery
的不足之处。
权宜解决方法:如果可能,请将 --universe_scope
设置为更严格的范围。例如:
# This command attempts to build the transitive closures of both //foo and # //bar. //bar uses an incoming edge transition to change its --cpu flag. $ bazel cquery 'somepath(//foo, //bar)' ERROR: Error doing post analysis query: Top-level targets //foo and //bar have different configurations (top-level targets with different configurations is not supported) # This command only builds the transitive closure of //foo, under which # //bar should exist in the correct configuration. $ bazel cquery 'somepath(//foo, //bar)' --universe_scope=//foo
不支持 --output=xml
。
非确定性输出。
cquery
不会自动清除之前命令中的 build 图,因此很容易提取过去查询的结果。例如,genrule
对其 tools
属性执行执行转换 - 也就是说,它在 exec 配置中配置其工具。
您可以在下方查看这种转换的延迟效果。
$ cat > foo/BUILD <<<EOF genrule( name = "my_gen", srcs = ["x.in"], outs = ["x.cc"], cmd = "$(locations :tool) $< >$@", tools = [":tool"], ) cc_library( name = "tool", ) EOF $ bazel cquery "//foo:tool" tool(target_config) $ bazel cquery "deps(//foo:my_gen)" my_gen (target_config) tool (exec_config) ... $ bazel cquery "//foo:tool" tool(exec_config)
解决方法:更改任何启动选项,以强制重新分析已配置的目标。
例如,将 --test_arg=<whatever>
添加到您的构建命令中。
问题排查
递归目标模式 (/...
)
如果您遇到以下情况:
$ bazel cquery --universe_scope=//foo:app "somepath(//foo:app, //foo/...)" ERROR: Error doing post analysis query: Evaluation failed: Unable to load package '[foo]' because package is not in scope. Check that all target patterns in query expression are within the --universe_scope of this query.
这会错误地提示软件包 //foo
不在范围内,即使 --universe_scope=//foo:app
包含该软件包也是如此。这是由于 cquery
中的设计限制所致。如需解决此问题,请在“universe”作用域中明确添加 //foo/...
:
$ bazel cquery --universe_scope=//foo:app,//foo/... "somepath(//foo:app, //foo/...)"
如果这不起作用(例如,由于 //foo/...
中的某些目标无法使用所选的构建标志进行构建),请使用预处理查询手动将模式展开为其组成软件包:
# Replace "//foo/..." with a subshell query call (not cquery!) outputting each package, piped into # a sed call converting "<pkg>" to "//<pkg>:*", piped into a "+"-delimited line merge. # Output looks like "//foo:*+//foo/bar:*+//foo/baz". # $ bazel cquery --universe_scope=//foo:app "somepath(//foo:app, $(bazel query //foo/... --output=package | sed -e 's/^/\/\//' -e 's/$/:*/' | paste -sd "+" -))"