Truy vấn có thể định cấu hình (cquery)

Báo cáo vấn đề Xem nguồn Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

cquery là một biến thể của query, xử lý chính xác select() và các hiệu ứng của lựa chọn xây dựng trên biểu đồ xây dựng.

Thao tác này đạt được bằng cách chạy trên kết quả của giai đoạn phân tích của Bazel, tích hợp các hiệu ứng này. Ngược lại, query chạy trên kết quả của giai đoạn tải của Bazel, trước khi các lựa chọn được đánh giá.

Ví dụ:

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

Mỗi kết quả đều có một giá trị nhận dạng duy nhất (9f87702) của cấu hình mà mục tiêu được tạo.

cquery chạy trên biểu đồ mục tiêu đã định cấu hình, nên nó không có thông tin chi tiết về các cấu phần phần mềm như thao tác xây dựng cũng như không có quyền truy cập vào các quy tắc test_suite vì đây không phải là các mục tiêu đã định cấu hình. Đối với trường hợp trước, hãy xem aquery.

Cú pháp cơ bản

Một lệnh gọi cquery đơn giản sẽ có dạng như sau:

bazel cquery "function(//target)"

Biểu thức truy vấn "function(//target)" bao gồm những nội dung sau:

  • function(...) là hàm sẽ chạy trên đích. cquery hỗ trợ hầu hết các chức năng của query, cùng với một số chức năng mới.
  • //target là biểu thức được truyền vào hàm. Trong ví dụ này, biểu thức là một mục tiêu đơn giản. Tuy nhiên, ngôn ngữ truy vấn cũng cho phép lồng các hàm. Hãy xem Hướng dẫn về truy vấn để biết các ví dụ.

cquery yêu cầu một mục tiêu chạy qua các giai đoạn tải và phân tích. Trừ phi được chỉ định khác, cquery sẽ phân tích(các) mục tiêu được liệt kê trong biểu thức truy vấn. Xem --universe_scope để truy vấn các phần phụ thuộc của mục tiêu bản dựng cấp cao nhất.

Cấu hình

Đường kẻ:

//tree:ash (9f87702)

có nghĩa là //tree:ash được tạo trong một cấu hình có mã 9f87702. Đối với hầu hết các mục tiêu, đây là một hàm băm không công khai của các giá trị tuỳ chọn bản dựng xác định cấu hình.

Để xem toàn bộ nội dung của cấu hình, hãy chạy:

$ bazel config 9f87702

9f87702 là tiền tố của mã nhận dạng đầy đủ. Điều này là do mã nhận dạng đầy đủ là hàm băm SHA-256, có độ dài và khó theo dõi. cquery hiểu mọi tiền tố hợp lệ của một mã nhận dạng hoàn chỉnh, tương tự như hàm băm ngắn Git. Để xem mã nhận dạng đầy đủ, hãy chạy $ bazel config.

Đánh giá mẫu mục tiêu

//foo có ý nghĩa khác đối với cquery so với query. Nguyên nhân là do cquery đánh giá các mục tiêu đã định cấu hình và biểu đồ bản dựng có thể có nhiều phiên bản //foo đã định cấu hình.

Đối với cquery, một mẫu mục tiêu trong biểu thức truy vấn sẽ đánh giá mọi mục tiêu được định cấu hình bằng một nhãn khớp với mẫu đó. Đầu ra là xác định, nhưng cquery không đảm bảo thứ tự ngoài hợp đồng sắp xếp truy vấn cốt lõi.

Thao tác này tạo ra kết quả tinh tế hơn cho các biểu thức truy vấn so với query. Ví dụ: những nội dung sau đây có thể tạo ra nhiều kết quả:

# 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)

Nếu bạn muốn khai báo chính xác phiên bản cần truy vấn, hãy dùng hàm config.

Hãy xem tài liệu về mẫu mục tiêu của query để biết thêm thông tin về các mẫu mục tiêu.

Hàm

Trong tập hợp các hàm do query hỗ trợ, cquery hỗ trợ tất cả các hàm, ngoại trừ allrdeps, buildfiles, rbuildfiles, siblings, testsvisible.

cquery cũng giới thiệu các hàm mới sau đây:

config

expr ::= config(expr, word)

Toán tử config cố gắng tìm mục tiêu đã định cấu hình cho nhãn được biểu thị bằng đối số đầu tiên và cấu hình do đối số thứ hai chỉ định.

Các giá trị hợp lệ cho đối số thứ hai là null hoặc băm cấu hình tuỳ chỉnh. Bạn có thể truy xuất các hàm băm từ $ bazel config hoặc đầu ra của cquery trước đó.

Ví dụ:

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

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

Nếu không tìm thấy tất cả kết quả của đối số đầu tiên trong cấu hình đã chỉ định, thì chỉ những kết quả có thể tìm thấy mới được trả về. Nếu không tìm thấy kết quả nào trong cấu hình đã chỉ định, thì truy vấn sẽ không thành công.

Tùy chọn

Tuỳ chọn bản dựng

cquery chạy trên một bản dựng Bazel thông thường và do đó, sẽ kế thừa tập hợp các lựa chọn có sẵn trong quá trình tạo bản dựng.

Sử dụng các lựa chọn cquery

--universe_scope (danh sách được phân tách bằng dấu phẩy)

Thông thường, các phần phụ thuộc của mục tiêu được định cấu hình sẽ trải qua các quá trình chuyển đổi, điều này khiến cấu hình của các phần phụ thuộc này khác với phần phụ thuộc của chúng. Cờ này cho phép bạn truy vấn một mục tiêu như thể mục tiêu đó được tạo dưới dạng một phần phụ thuộc hoặc một phần phụ thuộc bắc cầu của một mục tiêu khác. Ví dụ:

# 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 định cấu hình các công cụ của họ trong cấu hình exec để các truy vấn sau sẽ tạo ra các đầu ra sau:

Truy vấn Target Built Đầu ra
bazel cquery "//x:tool" //x:tool //x:tool(targetconfig)
bazel cquery "//x:tool" --universe_scope="//x:my_gen" //x:my_gen //x:tool(execconfig)

Nếu cờ này được đặt, nội dung của cờ sẽ được tạo. Nếu bạn không đặt thuộc tính này, thì tất cả các mục tiêu được đề cập trong biểu thức truy vấn sẽ được tạo. Đóng bắc cầu của các mục tiêu được tạo sẽ được dùng làm vũ trụ của truy vấn. Dù bằng cách nào, các mục tiêu cần tạo phải có thể tạo ở cấp cao nhất (tức là tương thích với các lựa chọn cấp cao nhất). cquery trả về kết quả trong bao đóng của các mục tiêu cấp cao nhất này.

Ngay cả khi có thể tạo tất cả các mục tiêu trong một biểu thức truy vấn ở cấp cao nhất, bạn cũng không nên làm như vậy. Ví dụ: việc đặt --universe_scope một cách rõ ràng có thể ngăn việc tạo mục tiêu nhiều lần trong các cấu hình mà bạn không quan tâm. Bạn cũng có thể chỉ định phiên bản cấu hình nào của mục tiêu mà bạn đang tìm kiếm. Bạn nên đặt cờ này nếu biểu thức truy vấn của bạn phức tạp hơn deps(//foo).

--implicit_deps (boolean, default=True)

Việc đặt cờ này thành false sẽ lọc ra tất cả những kết quả không được đặt rõ ràng trong tệp BUILD và thay vào đó được Bazel đặt ở nơi khác. Điều này bao gồm cả việc lọc các chuỗi công cụ đã phân giải.

--tool_deps (boolean, default=True)

Việc đặt cờ này thành false sẽ lọc ra tất cả các mục tiêu được định cấu hình mà đường dẫn từ mục tiêu được truy vấn đến các mục tiêu đó đi qua một quá trình chuyển đổi giữa cấu hình mục tiêu và cấu hình không phải mục tiêu. Nếu mục tiêu được truy vấn nằm trong cấu hình mục tiêu, thì việc đặt --notool_deps sẽ chỉ trả về những mục tiêu cũng nằm trong cấu hình mục tiêu. Nếu đích được truy vấn nằm trong cấu hình không phải đích, thì việc đặt --notool_deps sẽ chỉ trả về những đích cũng nằm trong cấu hình không phải đích. Chế độ cài đặt này thường không ảnh hưởng đến việc lọc các chuỗi công cụ đã phân giải.

--include_aspects (boolean, default=True)

Bao gồm các phần phụ thuộc do khía cạnh thêm vào.

Nếu cờ này bị tắt, cquery somepath(X, Y)cquery deps(X) | grep 'Y' sẽ bỏ qua Y nếu X chỉ phụ thuộc vào Y thông qua một khía cạnh.

Định dạng đầu ra

Theo mặc định, cquery xuất kết quả trong danh sách các cặp nhãn và cấu hình được sắp xếp theo thứ tự phụ thuộc. Ngoài ra, bạn cũng có thể hiển thị kết quả theo những cách khác.

Kiểu chuyển cảnh

--transitions=lite
--transitions=full

Chuyển đổi cấu hình được dùng để tạo các mục tiêu bên dưới các mục tiêu cấp cao nhất trong các cấu hình khác với các mục tiêu cấp cao nhất.

Ví dụ: một mục tiêu có thể áp đặt quá trình chuyển đổi sang cấu hình exec trên tất cả các phần phụ thuộc trong thuộc tính tools của mục tiêu đó. Đây được gọi là hiệu ứng chuyển đổi thuộc tính. Các quy tắc cũng có thể áp đặt các hiệu ứng chuyển đổi trên cấu hình của riêng chúng, được gọi là hiệu ứng chuyển đổi lớp quy tắc. Định dạng đầu ra này xuất thông tin về những quá trình chuyển đổi này, chẳng hạn như loại quá trình chuyển đổi và ảnh hưởng của quá trình chuyển đổi đến các lựa chọn xây dựng.

Định dạng đầu ra này được kích hoạt bằng cờ --transitions. Theo mặc định, cờ này được đặt thành NONE. Bạn có thể đặt chế độ này thành FULL hoặc LITE. Chế độ FULL xuất thông tin về các quá trình chuyển đổi lớp quy tắc và quá trình chuyển đổi thuộc tính, bao gồm cả sự khác biệt chi tiết của các lựa chọn trước và sau quá trình chuyển đổi. Chế độ LITE xuất cùng một thông tin mà không có sự khác biệt về các lựa chọn.

Đầu ra thông báo giao thức

--output=proto

Lựa chọn này khiến các mục tiêu kết quả được in ở dạng vùng đệm giao thức nhị phân. Bạn có thể xem định nghĩa về vùng đệm giao thức tại src/main/protobuf/analysis_v2.proto.

CqueryResult là thông báo cấp cao nhất chứa kết quả của cquery. Nó có danh sách các tin nhắn ConfiguredTarget và danh sách các tin nhắn Configuration. Mỗi ConfiguredTarget đều có một configuration_id có giá trị bằng với giá trị của trường id trong thông báo Configuration tương ứng.

--[no]proto:include_configurations

Theo mặc định, kết quả cquery sẽ trả về thông tin cấu hình trong mỗi mục tiêu được định cấu hình. Nếu bạn muốn bỏ qua thông tin này và nhận được đầu ra proto được định dạng giống hệt như đầu ra proto của truy vấn, hãy đặt cờ này thành false.

Hãy xem tài liệu về đầu ra proto của truy vấn để biết thêm các lựa chọn liên quan đến đầu ra proto.

Đầu ra đồ thị

--output=graph

Tuỳ chọn này tạo đầu ra dưới dạng tệp .dot tương thích với Graphviz. Hãy xem tài liệu đầu ra đồ thị của query để biết thông tin chi tiết. cquery cũng hỗ trợ --graph:node_limit--graph:factored.

Tệp đầu ra

--output=files

Lựa chọn này in danh sách các tệp đầu ra do mỗi mục tiêu được truy vấn tạo ra, tương tự như danh sách được in ở cuối lệnh gọi bazel build. Đầu ra chỉ chứa những tệp được quảng cáo trong các nhóm đầu ra được yêu cầu theo quyết định của cờ --output_groups. Nó có chứa các tệp nguồn.

Tất cả các đường dẫn do định dạng đầu ra này phát ra đều tương ứng với execroot. Bạn có thể lấy đường dẫn này thông qua bazel info execution_root. Nếu có symlink bazel-out tiện lợi, thì đường dẫn đến các tệp trong kho lưu trữ chính cũng sẽ phân giải tương ứng với thư mục không gian làm việc.

Xác định định dạng đầu ra bằng Starlark

--output=starlark

Định dạng đầu ra này gọi một hàm Starlark cho mỗi mục tiêu được định cấu hình trong kết quả truy vấn và in giá trị do lệnh gọi trả về. Cờ --starlark:file chỉ định vị trí của một tệp Starlark xác định một hàm có tên là format với một tham số duy nhất, target. Hàm này được gọi cho mỗi Target trong kết quả truy vấn. Ngoài ra, để thuận tiện, bạn có thể chỉ định phần nội dung của một hàm được khai báo là def format(target): return expr bằng cách sử dụng cờ --starlark:expr.

Phương ngữ Starlark "cquery"

Môi trường Starlark cquery khác với tệp BUILD hoặc .bzl. Ngôn ngữ này bao gồm tất cả các hằng số và hàm tích hợp Starlark cốt lõi, cộng với một số hằng số và hàm dành riêng cho cquery được mô tả bên dưới, nhưng không bao gồm (ví dụ) glob, native hoặc rule và không hỗ trợ câu lệnh tải.

build_options(target)

build_options(target) trả về một bản đồ có các khoá là giá trị nhận dạng lựa chọn xây dựng (xem Cấu hình) và các giá trị là giá trị Starlark của chúng. Các tuỳ chọn bản dựng có giá trị không phải là giá trị Starlark hợp lệ sẽ bị bỏ qua khỏi bản đồ này.

Nếu đích đến là một tệp đầu vào, build_options(target) sẽ trả về None, vì đích đến của tệp đầu vào có cấu hình rỗng.

providers(target)

providers(target) trả về một bản đồ có khoá là tên của nhà cung cấp (ví dụ: "DefaultInfo") và có giá trị là các giá trị Starlark của nhà cung cấp đó. Những nhà cung cấp có giá trị không phải là giá trị Starlark hợp lệ sẽ bị loại khỏi bản đồ này.

Ví dụ

In danh sách tên cơ sở được phân tách bằng dấu cách của tất cả các tệp do //foo tạo ra:

  bazel cquery //foo --output=starlark \
    --starlark:expr="' '.join([f.basename for f in providers(target)['DefaultInfo'].files.to_list()])"

In danh sách các đường dẫn của tất cả các tệp do các mục tiêu quy tắc tạo ra trong //bar và các gói con của nó, được phân tách bằng dấu cách:

  bazel cquery 'kind(rule, //bar/...)' --output=starlark \
    --starlark:expr="' '.join([f.path for f in providers(target)['DefaultInfo'].files.to_list()])"

In danh sách các phím tắt của tất cả các thao tác do //foo đăng ký.

  bazel cquery //foo --output=starlark \
    --starlark:expr="[a.mnemonic for a in target.actions]"

In danh sách các kết quả biên dịch do cc_library //baz đăng ký.

  bazel cquery //baz --output=starlark \
    --starlark:expr="[f.path for f in target.output_groups.compilation_outputs.to_list()]"

In giá trị của tuỳ chọn dòng lệnh --javacopt khi tạo //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="build_options(target)['//command_line_option:javacopt']"

In nhãn của mỗi mục tiêu với đúng một đầu ra. Ví dụ này sử dụng các hàm Starlark được xác định trong một tệp.

  $ cat example.cquery

  def has_one_output(target):
    return len(providers(target)["DefaultInfo"].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

In nhãn của từng mục tiêu hoàn toàn là Python 3. Ví dụ này sử dụng các hàm Starlark được xác định trong một tệp.

  $ 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

Trích xuất giá trị từ một Nhà cung cấp do người dùng xác định.

  $ 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 so với query

cqueryquery bổ trợ cho nhau và có thế mạnh ở những lĩnh vực khác nhau. Hãy cân nhắc những yếu tố sau để quyết định lựa chọn phù hợp với bạn:

  • cquery tuân theo các nhánh select() cụ thể để mô hình hoá chính xác biểu đồ mà bạn tạo. query không biết bản dựng chọn nhánh nào, nên ước tính quá mức bằng cách đưa tất cả các nhánh vào.
  • Độ chính xác của cquery yêu cầu xây dựng nhiều phần của biểu đồ hơn query. Cụ thể, cquery đánh giá các mục tiêu đã định cấu hình trong khi query chỉ đánh giá các mục tiêu. Việc này tốn nhiều thời gian và bộ nhớ hơn.
  • Việc cquery diễn giải ngôn ngữ truy vấn gây ra sự mơ hồ mà query tránh được. Ví dụ: nếu "//foo" có trong 2 cấu hình, thì cquery "deps(//foo)" nên dùng cấu hình nào? Hàm config có thể giúp bạn làm việc này.

Đầu ra không xác định

cquery không tự động xoá biểu đồ bản dựng khỏi các lệnh trước đó. Do đó, tính năng này có xu hướng chọn kết quả từ các truy vấn trước đây.

Ví dụ: genrule thực hiện một quá trình chuyển đổi exec trên thuộc tính tools của nó – tức là nó định cấu hình các công cụ của mình trong cấu hình exec.

Bạn có thể thấy những ảnh hưởng kéo dài của quá trình chuyển đổi đó ở bên dưới.

$ cat > foo/BUILD <<

Đây có thể là hành vi mong muốn hoặc không mong muốn, tuỳ thuộc vào nội dung bạn đang cố gắng đánh giá.

Để tắt tính năng này, hãy chạy blaze clean trước cquery để đảm bảo có một biểu đồ phân tích mới.

Khắc phục sự cố

Mẫu mục tiêu đệ quy (/...)

Nếu bạn gặp phải:

$ 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.

điều này đề xuất không chính xác rằng gói //foo không nằm trong phạm vi mặc dù --universe_scope=//foo:app có bao gồm gói này. Nguyên nhân là do những hạn chế về thiết kế trong cquery. Để giải quyết vấn đề này, hãy đưa //foo/... vào phạm vi vũ trụ một cách rõ ràng:

$ bazel cquery --universe_scope=//foo:app,//foo/... "somepath(//foo:app, //foo/...)"

Nếu cách này không hiệu quả (ví dụ: vì một số mục tiêu trong //foo/... không thể tạo bằng các cờ bản dựng đã chọn), hãy tự động giải gói mẫu thành các gói cấu thành bằng một truy vấn tiền xử lý:

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