2022 年 BazelCon 将于 11 月 16 日至 17 日在纽约和线上举办。
立即报名!

操作图查询 (aquery)

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

您可以使用 aquery 命令查询构建图中的操作。它对分析后配置目标图执行操作,并公开有关操作、工件及其关系的信息。

如果您对通过配置的目标图表生成的操作/工件的属性感兴趣,aquery 非常有用。例如,运行实际命令及其输入/输出/助记符。

此工具接受多个命令行选项。值得注意的是,query 命令在常规 Bazel 构建之上运行,并继承构建期间可用的选项集。

它不仅支持传统 query 中的一组函数,还支持 siblingsbuildfilestests

aquery 输出示例(不含具体详细信息):

$ bazel aquery 'deps(//some:label)'
action 'Writing file some_file_name'
  Mnemonic: ...
  Target: ...
  Configuration: ...
  ActionKey: ...
  Inputs: [...]
  Outputs: [...]

基本语法

aquery 的语法简单示例如下:

bazel aquery "aquery_function(function(//target))"

查询表达式(用引号括起来)包含以下内容:

  • aquery_function(...)aquery 专用的函数。如需了解详情,请参阅下文
  • function(...):与传统 query 相同的标准函数
  • //target 是感兴趣的目标的标签。
# aquery examples:
# Get the action graph generated while building //src/target_a
$ bazel aquery '//src/target_a'

# Get the action graph generated while building all dependencies of //src/target_a
$ bazel aquery 'deps(//src/target_a)'

# Get the action graph generated while building all dependencies of //src/target_a
# whose inputs filenames match the regex ".*cpp".
$ bazel aquery 'inputs(".*cpp", deps(//src/target_a))'

使用查询函数

aquery 函数有三个:

  • inputs:按输入过滤操作。
  • outputs:按输出过滤操作
  • mnemonic:按助记符过滤操作

expr ::= inputs(word, expr)

inputs 运算符会返回通过构建 expr 生成的操作,这些输入的文件名与 word 提供的正则表达式匹配。

$ bazel aquery 'inputs(".*cpp", deps(//src/target_a))'

outputsmnemonic 函数具有相似的语法。

您还可以通过组合函数来实现 AND 运算。例如:

  $ bazel aquery 'mnemonic("Cpp.*", (inputs(".*cpp", inputs("foo.*", //src/target_a))))'

上面的命令将查找构建 //src/target_a 时涉及的所有操作,这些助记符与 "Cpp.*" 匹配,输入与 ".*cpp""foo.*" 模式匹配。

生成的语法错误示例如下:

        $ bazel aquery 'deps(inputs(".*cpp", //src/target_a))'
        ERROR: aquery filter functions (inputs, outputs, mnemonic) produce actions,
        and therefore can't be the input of other function types: deps
        deps(inputs(".*cpp", //src/target_a))

选项

构建选项

aquery 在常规 Bazel build 上运行,因此会沿用 build 期间可用的一组选项

查询选项

--output=(text|summary|proto|jsonproto|textproto), default=text

默认输出格式 (text) 为简单易懂的格式,请使用 prototextprotojsonproto 作为机器可读的格式。proto 消息为 analysis.ActionGraphContainer

--include_commandline, default=true

在输出中包含操作命令行的内容(可能很大)。

--include_artifacts, default=true

包括操作输入和输出(可能较大)的输入名称。

--include_aspects, default=true

是否在输出中包含宽高比生成的操作。

--include_param_files, default=false

添加命令中使用的参数文件的内容(可能很大)。

--include_file_write_contents, default=false

添加 actions.write() 操作的文件内容以及 SourceSymlinkManifest 操作的清单文件内容。在 file_contents 字段中返回文件内容并使用 --output=xxxproto。使用 --output=text 时,输出有 FileWriteContents: [<base64-encoded file contents>]

--skyframe_state, default=false

在不执行额外分析的情况下,从 Skyframe 中转储操作图。

其他工具和功能

针对 Skyframe 状态进行查询

Skyframe 是 Bazel 的评估和增量模型。在 Bazel 服务器的每个实例上,Skyframe 会存储根据先前的 Analysis 阶段运行所构建的依赖关系图。

在某些情况下,查询 Skyframe 的操作图会很有用。用例示例:

  1. 运行:bazel build //target_a
  2. 运行:bazel build //target_b
  3. 已生成文件“foo.out”。

作为 Bazel 用户,我想确定 foo.out 是通过构建 //target_a 还是通过 //target_b 生成的。

用户可以运行 bazel aquery 'outputs("foo.out", //target_a)'bazel aquery 'outputs("foo.out", //target_b)' 来确定负责创建 foo.out 的操作,从而确定目标。但是,之前构建的不同目标的数量可能大于 2,这使得运行多个 aquery 命令变得很麻烦。

或者,您也可以使用 --skyframe_state 标志:

  # List all actions on Skyframe's action graph
  $ bazel aquery --output=proto --skyframe_state

  # or

  # List all actions on Skyframe's action graph, whose output matches "foo.out"
  $ bazel aquery --output=proto --skyframe_state 'outputs("foo.out")'

--skyframe_state 模式下,aquery 会接受 Skyframe 在 Bazel 实例上保留的 Action Graph 的内容,并视需要对其执行过滤并输出内容,而无需重新运行分析阶段。

特殊注意事项

输出格式

--skyframe_state 目前仅适用于 --output=proto--output=textproto

查询表达式中不包含目标标签

目前,无论目标是什么,--skyframe_state 都会查询 Skyframe 中存在的整个操作图。将查询中指定的目标标签与 --skyframe_state 一起视为语法错误:

  # WRONG: Target Included
  $ bazel aquery --output=proto --skyframe_state **//target_a**
  ERROR: Error while parsing '//target_a)': Specifying build target(s) [//target_a] with --skyframe_state is currently not supported.

  # WRONG: Target Included
  $ bazel aquery --output=proto --skyframe_state 'inputs(".*.java", **//target_a**)'
  ERROR: Error while parsing '//target_a)': Specifying build target(s) [//target_a] with --skyframe_state is currently not supported.

  # CORRECT: Without Target
  $ bazel aquery --output=proto --skyframe_state
  $ bazel aquery --output=proto --skyframe_state 'inputs(".*.java")'

比较查询输出

您可以使用 aquery_differ 工具比较两种不同的查询调用的输出。例如:当您对规则定义进行一些更改并希望验证正在运行的命令行是否发生更改时。aquery_differ 是实现此目的的工具。

该工具位于 bazelbuild/bazel 代码库中。如需使用代码库,请将代码库克隆到本地机器。用法示例:

  $ bazel run //tools/aquery_differ -- \
  --before=/path/to/before.proto \
  --after=/path/to/after.proto \
  --input_type=proto \
  --attrs=cmdline \
  --attrs=inputs

上面的命令会返回查询输出的 beforeafter 之间的区别:哪些操作在一个但不存在于另一个操作中,哪些操作在每个查询输出中具有不同的命令行/输入,等等。运行上述命令的结果如下:

  Aquery output 'after' change contains an action that generates the following outputs that aquery output 'before' change doesn't:
  ...
  /list of output files/
  ...

  [cmdline]
  Difference in the action that generates the following output(s):
    /path/to/abc.out
  --- /path/to/before.proto
  +++ /path/to/after.proto
  @@ -1,3 +1,3 @@
    ...
    /cmdline diff, in unified diff format/
    ...

命令选项

--before, --after:要比较的查询输出文件

--input_type=(proto|text_proto), default=proto:输入文件的格式。为 prototextproto 查询输出提供支持。

--attrs=(cmdline|inputs), default=cmdline:要比较的操作的属性。

宽高比

其他 Aspect 可以相互重叠。然后,这些 Aspect 生成的操作的查询输出将包含 Aspect 路径,这是应用于生成该操作的目标的 Aspect 路径。

宽高比方面的示例:

  t0
  ^
  | <- a1
  t1
  ^
  | <- a2
  t2

让我们 ti 成为规则 ri 的目标,它将一个方面 ai 应用于其依赖项。

假设 a2 在应用于目标 t0 时生成操作 X,操作 X 的 bazel aquery --include_aspects 'deps(//t2)' 的文本输出将为:

  action ...
  Mnemonic: ...
  Target: //my_pkg:t0
  Configuration: ...
  AspectDescriptors: [//my_pkg:rule.bzl%**a2**(foo=...)
    -> //my_pkg:rule.bzl%**a1**(bar=...)]
  ...

这意味着操作 X 由应用于 a1(t0) 的宽高比 a2 生成,其中 a1(t0) 是应用于目标 t0 的宽高比 a1 的结果。

每个 AspectDescriptor 都具有以下格式:

  AspectClass([param=value,...])

AspectClass 可以是 Aspect 类的名称(对于原生 Aspect)或 bzl_file%aspect_name(对于 Starlark 方面)。AspectDescriptor 按照依赖项图的拓扑顺序排序。

与 JSON 配置文件关联

当查询提供 build 中运行的操作(为什么正在运行、其输入/输出)的相关信息时,JSON 配置文件会告诉我们操作的执行时间和时长。您可以通过通用标准(操作的主要输出)来合并这两组信息。

如需在 JSON 配置文件中包含操作和输出,请使用 --experimental_include_primary_output --noexperimental_slim_json_profile 生成该配置文件。精简配置文件与包含主要输出的功能不兼容。操作的主要输出默认包含在查询中。

我们目前不提供用于组合这两个数据源的规范工具,但您应该能够使用上述信息构建自己的脚本。

已知问题

处理共享操作

有时,操作会在配置的目标之间共享。

在执行阶段,系统会将这些共享操作视为一次并仅执行一次。但是,查询对执行前分析后操作图执行操作,因此将它们视为单独的操作,其输出工件的 execPath 完全相同。因此,等效工件重复。

您可以在 GitHub 上找到查询问题/计划推出的功能的列表。

常见问题解答

即使输入文件的内容发生更改,ActionKey 仍将保持不变。

在查询的上下文中,ActionKey 是指从 ActionAnalysisMetadata#getKey 获取的 String

  Returns a string encoding all of the significant behaviour of this Action that might affect the
  output. The general contract of `getKey` is this: if the work to be performed by the
  execution of this action changes, the key must change.

  ...

  Examples of changes that should affect the key are:

  - Changes to the BUILD file that materially affect the rule which gave rise to this Action.
  - Changes to the command-line options, environment, or other global configuration resources
      which affect the behaviour of this kind of Action (other than changes to the names of the
      input/output files, which are handled externally).
  - An upgrade to the build tools which changes the program logic of this kind of Action
      (typically this is achieved by incorporating a UUID into the key, which is changed each
      time the program logic of this action changes).
  Note the following exception: for actions that discover inputs, the key must change if any
  input names change or else action validation may falsely validate.

这不包括对输入文件的内容做出的更改,不要与 RemoteCacheClient#ActionKey 混淆。

更新

如有任何问题/功能请求,请在此处提交问题。