可配置查询 (cquery)

cqueryquery 的变体,可正确处理 select() 和构建选项对构建图的影响。

为此,它会分析 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 在配置的目标图上运行,因此它无法深入了解构建操作等工件,也无法访问 [test_suite](/versions/6.2.0/reference/be/general#test_suite) 规则,因为它们不是配置的目标。对于前者,请参阅 [aquery](/versions/6.2.0/docs/aquery)

基本语法

一个简单的 cquery 调用如下所示:

bazel cquery "function(//target)"

查询表达式 "function(//target)" 包含以下内容:

  • function(...) 是要对目标运行的函数。cquery 支持 query 的大多数函数,还支持一些新函数。
  • //target 是馈送给函数的表达式。在此示例中,表达式是一个简单的目标。但是,查询语言还允许嵌套函数。如需查看相关示例,请参阅查询操作方法

cquery 要求目标经历加载和分析阶段。除非另有说明,否则 cquery 会解析查询表达式中列出的目标。如需了解如何查询顶级构建目标的依赖项,请参阅 --universe_scope

配置

以下代码行:

//tree:ash (9f87702)

表示 //tree:ash 是在 ID 为 9f87702 的配置中构建的。对于大多数目标,这是定义配置的构建选项值的不透明哈希。

如需查看配置的完整内容,请运行以下命令:

$ bazel config 9f87702

主机配置使用特殊 ID (HOST)。非生成的源文件(如 srcs 中常见的那些文件)会使用特殊 ID (null)(因为不需要对其进行配置)。

9f87702 是完整 ID 的前缀。这是因为完整 ID 是 SHA-256 哈希,这些哈希很长,并且难以遵循。cquery 可以理解完整 ID 的任何有效前缀,类似于 Git 短哈希。如需查看完整 ID,请运行 $ bazel config

目标模式评估

//foocquery 的含义与 query 的含义不同。这是因为 cquery 会评估已配置的目标,而构建图可能具有多个 //foo 的已配置版本。

对于 cquery,查询表达式中的目标模式会针对具有与该模式匹配的标签的每个已配置的目标进行求值。输出是确定的,但 cquery 无法在核心查询排序协定之外保证排序。

与使用 query 相比,这会使查询表达式的结果更细微。例如,以下命令可以生成多个结果:

# Analyzes //foo in the target configuration, but also analyzes
# //genrule_with_foo_as_tool which depends on a host-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 (HOST)

如果要精确声明要查询的实例,请使用 config 函数。

如需详细了解目标模式,请参阅 query目标模式文档

函数

query 支持的一组函数中,cquery 支持除 visiblesiblingsbuildfilestests 之外的所有函数。

cquery 还引入了以下新函数:

config

expr ::= config(expr, word)

config 运算符尝试为由第一个参数和第二个参数指定的配置表示的标签查找已配置的目标。

第二个参数的有效值是 targethostnull自定义配置哈希。可以从 $ bazel config 或上一个 cquery 的输出中检索哈希。

示例:

$ bazel cquery "config(//bar, host)" --universe_scope=//foo
$ bazel cquery "deps(//foo)"
//bar (HOST)
//baz (3732cc8)

$ bazel cquery "config(//baz, 3732cc8)"

如果无法在指定配置中找到第一个参数的所有结果,则仅返回能够找到的结果。如果在指定配置中找不到结果,则查询会失败。

选项

构建选项

cquery 在常规 Bazel 构建上运行,因此会继承构建期间可用的一组选项。

使用 cquery 选项

--universe_scope(逗号分隔列表)

已配置目标的依赖项通常会经历转换,这会导致其配置与从属目标有所不同。借助此标志,您可以查询目标,就像它是作为另一个目标的依赖项或传递依赖项构建的一样。例如:

# x/BUILD
genrule(
     name = "my_gen",
     srcs = ["x.in"],
     outs = ["x.cc"],
     cmd = "$(locations :tool) $< >$@",
     tools = [":tool"],
)
cc_library(
    name = "tool",
)

Genrule 在主机配置中配置其工具,因此以下查询会生成以下输出:

查询 构建目标 输出
bazel cquery "//x:tool" //x:tool //x:tool(targetconfig)
bazel cquery "//x:tool" --universe_scope="//x:my_gen" //x:my_gen //x:tool(hostconfig)

如果设置了此标志,系统会构建其内容。如果未设置此参数,系统会改为构建查询表达式中提到的所有目标。已构建目标的传递闭包用作查询的集合。无论采用哪种方式,要构建的目标都必须可在顶层构建(即与顶级选项兼容)。cquery 会返回这些顶级目标的传递闭包的结果。

即使可以在顶级查询表达式中构建所有目标,不这样做也可能有好处。例如,明确设置 --universe_scope 可防止在您不关注的配置中多次构建目标。这还可以帮助您指定要查找的目标的配置版本(因为目前无法以任何其他方式完全指定此选项)。如果您的查询表达式比 deps(//foo) 复杂,则应设置此标志。

--implicit_deps(布尔值,default=True)

将此标志设置为 false 会过滤掉未在 BUILD 文件中明确设置、而由 Bazel 的其他位置设置的所有结果。这包括过滤已解析的工具链。

--tool_deps(布尔值,default=True)

将此标志设置为 false 会过滤掉从查询的目标到目标配置之间的所有已配置目标的路径在目标配置与非目标配置之间的转换时。如果查询的目标位于目标配置中,则设置 --notool_deps 将仅返回也位于目标配置中的目标。如果查询的目标属于非目标配置,则设置 --notool_deps 将仅返回也属于非目标配置的目标。此设置通常不会影响对已解析工具链的过滤。

--include_aspects(布尔值,default=True)

方面可以向 build 添加其他依赖项。默认情况下,cquery 不会遵循切面,因为它们会使可查询图变大,而这会使用更多内存。但遵循这些最佳实践可以得到更准确的结果。

如果您不担心大型查询对内存的影响,请在 bazelrc 中默认启用此标志。

如果在构建目标 Y 的情况下进行查询,则可能会遇到目标 X 在构建目标 Y 时失败,但 cquery somepath(Y, X)cquery deps(Y) | grep 'X' 不会返回任何结果的问题,因为依赖关系是通过切面出现。

输出格式

默认情况下,cquery 会输出按依赖项排序的标签和配置对列表。您还可以通过其他方式显示结果。

转场效果

--transitions=lite
--transitions=full

配置转换用于在顶级目标下构建与顶级目标不同的配置。

例如,目标可能会在其 tools 属性中对所有依赖项强制转换为主机配置。这称为属性转换。规则还可以对自己的配置实施转换,称为规则类转换。此输出格式会输出有关这些转换的信息,例如转换的类型及其对构建选项的影响。

此输出格式由 --transitions 标志触发,该标志默认设置为 NONE。可以设置为 FULLLITE 模式。FULL 模式会输出有关规则类转换和属性转换的信息,包括转换前后的选项的详细差异。LITE 模式会输出相同的信息,而不会显示选项差异。

协议消息输出

--output=proto

此选项会使生成的目标以二进制协议缓冲区形式输出。您可以在 src/main/protobuf/analysis.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 标志确定的所请求输出组中通告的文件。但包含源文件。

使用 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 的常量和函数,但不包括 globnativerule 等,并且它不支持 load 语句。

build_options(target)

build_options(target) 会返回一个映射,其键为 build 选项标识符(请参阅配置),其值为对应的 Starlark 值。此映射中省略了值不是合法 Starlark 值的 build 选项。

如果目标是输入文件,则 build_options(target) 会返回 None,因为输入文件目标的配置为 null。

提供商(目标)

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 与查询

cqueryquery 互为补充,在不同的领域表现出色。请考虑以下因素来判断哪种设置适合您:

  • cquery 遵循特定的 select() 分支来为您构建的确切图建模。query 不知道 build 选择的是哪个分支,因此通过纳入所有分支来过度近似。
  • query 相比,cquery 的精确度要求构建更多的图。具体而言,cquery 会评估配置的目标,而 query 只会评估目标。这会花费更多时间并使用更多内存。
  • cquery查询语言的解释引入了 query 可以避免的歧义。例如,如果两个配置中存在 "//foo"cquery "deps(//foo)" 应使用哪种配置? [config](#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 不会自动擦除先前命令中的构建图,因此很容易从过往查询中获取结果。例如,genquery 对其 tools 属性执行主机转换,也就是说,它会在主机配置中配置其工具。

您可以在下方查看这种过渡的持久效果。

$ 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 (host_config)
...

    $ bazel cquery "//foo:tool"
tool(host_config)

权宜解决方法:更改任何启动选项,以强制重新分析配置的目标。 例如,在构建命令中添加 --test_arg=&lt;whatever&gt;

问题排查

递归目标模式 (/...)

如果您遇到以下情况:

$ 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 "+" -))"