可設定的查詢 (cquery)

回報問題 查看原始碼 Nightly · 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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)

每項結果都會包含用於建構目標的設定專屬 ID (9f87702)

由於 cquery 會在已設定的目標圖上執行,因此不會深入瞭解建構動作等構件,也不會存取 [test_suite](/versions/6.1.0/reference/be/general#test_suite) 規則,因為這些不是已設定的目標。如需瞭解前者,請參閱 [aquery](/versions/6.1.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

目標模式評估

//foocqueryquery 中的含義不同。這是因為 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 支援所有函式,但不支援 allrdepsbuildfilesrbuildfilessiblingstestsvisiblecquery 也推出了下列新函式:

config

expr ::= config(expr, word)

config 運算子會嘗試為第一個引數所表示的標籤和第二個引數指定的設定,找出已設定的目標。

第二個引數的有效值為 targethostnull自訂設定雜湊。雜湊可以從 $ bazel configcquery 先前的輸出內容中擷取。

範例:

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

Genrules 會在主機設定中設定工具,因此下列查詢會產生以下輸出內容:

查詢 已建構目標 輸出
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 (布林值,預設值為 True)

將此標記設為 false 可篩除所有未在 BUILD 檔案中明確設定,而是由 Bazel 在其他位置設定的結果。這包括篩選已解析的工具鏈。

--tool_deps (布林值,預設值為 True)

將此標記設為 false 可篩除所有已設定的目標,這些目標的路徑從查詢的目標跨越目標設定和非目標設定之間的轉換。如果查詢的目標位於目標設定中,設定 --notool_deps 只會傳回同時位於目標設定中的目標。如果查詢的目標位於非目標設定中,設定 --notool_deps 只會傳回位於非目標設定中的目標。這項設定通常不會影響已解析工具鍊的篩選。

--include_aspects (布林值,預設值為 True)

切面可在建構中新增其他依附元件。根據預設,cquery 不會追蹤切面,因為切面會使可查詢的圖形變大,進而占用更多記憶體。但遵循這些規則可產生更準確的結果。

如果您不擔心大型查詢對記憶體的影響,請在 bazelrc 中預設啟用此標記。

如果您在查詢時停用面向,可能會遇到以下問題:目標 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,也不支援載入陳述式。

build_options(target)

build_options(target) 會傳回一個對應,其中鍵為建構選項 ID (請參閱「設定」),值則為 Starlark 值。此對應表會略過值不是合法 Starlark 值的建構選項。

如果目標為輸入檔案,則 build_options(target) 會傳回 None,因為輸入檔案目標的設定為空值。

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 與查詢

cqueryquery 彼此相輔相成,適合各種分眾市場。請考量以下因素,決定適合您的選項:

  • cquery 會追蹤特定 select() 分支,以模擬您建構的確切圖表。query 不知道建構作業選擇哪個分支,因此會納入所有分支來進行過度近似處理。
  • cquery 的準確度需要建構比 query 更多的圖表。具體來說,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.

這錯誤顯示即使 --universe_scope=//foo:app 含有套件 //foo,套件也不在範圍內。這是因為 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 "+" -))"