Thuộc tính bản dựng có thể định cấu hình

Báo cáo vấn đề Xem nguồn Hằng đêm · 7,4 của Google. 7.3 · 7.2 · 7.1 · 7 · 6,5

Thuộc tính có thể định cấu hình, thường gọi là select(), là một tính năng của Bazel cho phép người dùng chuyển đổi giữa các giá trị của các thuộc tính quy tắc tạo ở dòng lệnh.

Ví dụ: bạn có thể sử dụng tính năng này cho một thư viện đa nền tảng tự động chọn cách triển khai phù hợp cho cấu trúc hoặc cho một tệp nhị phân có thể định cấu hình tính năng có thể được tuỳ chỉnh tại thời điểm tạo bản dựng.

Ví dụ:

# myapp/BUILD

cc_binary(
    name = "mybinary",
    srcs = ["main.cc"],
    deps = select({
        ":arm_build": [":arm_lib"],
        ":x86_debug_build": [":x86_dev_lib"],
        "//conditions:default": [":generic_lib"],
    }),
)

config_setting(
    name = "arm_build",
    values = {"cpu": "arm"},
)

config_setting(
    name = "x86_debug_build",
    values = {
        "cpu": "x86",
        "compilation_mode": "dbg",
    },
)

Thao tác này khai báo một cc_binary "chọn" các phần phụ thuộc dựa trên cờ tại dòng lệnh. Cụ thể, deps trở thành:

Lệnh phần phụ thuộc =
bazel build //myapp:mybinary --cpu=arm [":arm_lib"]
bazel build //myapp:mybinary -c dbg --cpu=x86 [":x86_dev_lib"]
bazel build //myapp:mybinary --cpu=ppc [":generic_lib"]
bazel build //myapp:mybinary -c dbg --cpu=ppc [":generic_lib"]

select() đóng vai trò là phần giữ chỗ cho một giá trị sẽ được chọn dựa trên các điều kiện cấu hình. Đây là các nhãn tham chiếu đến mục tiêu config_setting. Bằng cách sử dụng select() trong một thuộc tính có thể định cấu hình, thuộc tính này sẽ áp dụng hiệu quả các giá trị khác nhau khi các điều kiện khác nhau được đáp ứng.

Kết quả trùng khớp phải rõ ràng: nếu nhiều điều kiện khớp thì * Tất cả các giá trị này đều đưa ra cùng một giá trị. Ví dụ: khi chạy trên linux x86, đây là {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} rõ ràng vì cả hai nhánh đều phân giải thành "hello". * values của một là tập mẹ nghiêm ngặt của tất cả các tập khác. Ví dụ: values = {"cpu": "x86", "compilation_mode": "dbg"} là một chuyên môn rõ ràng của values = {"cpu": "x86"}.

Điều kiện tích hợp //conditions:default sẽ tự động so khớp khi không có điều kiện nào khác khớp.

Mặc dù ví dụ này sử dụng deps, nhưng select() cũng hoạt động trên srcs, resources, cmd và hầu hết các thuộc tính khác. Chỉ một số ít thuộc tính không thể định cấu hình và các thuộc tính này được chú thích rõ ràng. Ví dụ: thuộc tính values của config_setting không thể định cấu hình.

select() và các phần phụ thuộc

Một số thuộc tính thay đổi tham số bản dựng cho tất cả các phần phụ thuộc bắc cầu theo một mục tiêu. Ví dụ: tools của genrule thay đổi --cpu thành CPU của máy chạy Bazel (nhờ tính năng biên dịch chéo, có thể khác với CPU mà mục tiêu được tạo). Đây được gọi là chuyển đổi cấu hình.

Đã cho

#myapp/BUILD

config_setting(
    name = "arm_cpu",
    values = {"cpu": "arm"},
)

config_setting(
    name = "x86_cpu",
    values = {"cpu": "x86"},
)

genrule(
    name = "my_genrule",
    srcs = select({
        ":arm_cpu": ["g_arm.src"],
        ":x86_cpu": ["g_x86.src"],
    }),
    tools = select({
        ":arm_cpu": [":tool1"],
        ":x86_cpu": [":tool2"],
    }),
)

cc_binary(
    name = "tool1",
    srcs = select({
        ":arm_cpu": ["armtool.cc"],
        ":x86_cpu": ["x86tool.cc"],
    }),
)

đang chạy

$ bazel build //myapp:my_genrule --cpu=arm

trên máy của nhà phát triển x86 liên kết bản dựng với g_arm.src, tool1x86tool.cc. Cả hai select đính kèm vào my_genrule đều sử dụng các tham số bản dựng của my_genrule, bao gồm cả --cpu=arm. Thuộc tính tools thay đổi --cpu đến x86 cho tool1 và các phần phụ thuộc bắc cầu của nó. select bật tool1 sử dụng các tham số bản dựng của tool1, bao gồm cả --cpu=x86.

Điều kiện cấu hình

Mỗi khoá trong thuộc tính có thể định cấu hình là một tham chiếu nhãn đến một config_setting hoặc constraint_value.

config_setting chỉ là một tập hợp các chế độ cài đặt cờ dòng lệnh dự kiến. Bằng cách gộp những thông tin này lại trong một mục tiêu, dễ duy trì "tiêu chuẩn" mà người dùng có thể tham khảo từ nhiều nơi.

constraint_value hỗ trợ hành vi trên nhiều nền tảng.

Cờ tích hợp

Các cờ như --cpu được tích hợp vào Bazel: công cụ xây dựng này hiểu rõ nguyên bản cho tất cả các bản dựng trong mọi dự án. Các thuộc tính này được chỉ định bằng thuộc tính values của config_setting:

config_setting(
    name = "meaningful_condition_name",
    values = {
        "flag1": "value1",
        "flag2": "value2",
        ...
    },
)

flagN là tên cờ (không có --, vì vậy là "cpu" thay vì "--cpu"). valueN là giá trị dự kiến cho cờ đó. :meaningful_condition_name khớp nếu mọi mục nhập trong values khớp. Thứ tự không liên quan.

valueN được phân tích cú pháp như thể được đặt trên dòng lệnh. Điều này có nghĩa là:

  • values = { "compilation_mode": "opt" } đối sánh bazel build -c opt
  • values = { "force_pic": "true" } đối sánh bazel build --force_pic=1
  • values = { "force_pic": "0" } đối sánh bazel build --noforce_pic

config_setting chỉ hỗ trợ các cờ ảnh hưởng đến hành vi của mục tiêu. Ví dụ: Không được phép sử dụng --show_progress vì nó chỉ ảnh hưởng đến cách Bazel báo cáo tiến trình cho người dùng. Các mục tiêu không được sử dụng thông tin đó gắn cờ để tạo kết quả của chúng. Không ghi lại chính xác tập hợp cờ được hỗ trợ. Trong thực tế, hầu hết những cờ "có ý nghĩa" cơ quan.

Cờ tuỳ chỉnh

Bạn có thể lập mô hình các cờ dành riêng cho dự án của riêng mình bằng Chế độ cài đặt bản dựng Starlark. Không giống như cờ tích hợp, đây là được xác định là mục tiêu bản dựng, vì vậy Bazel tham chiếu chúng bằng nhãn mục tiêu.

Các lượt chuyển đổi này được kích hoạt bằng thông số của config_setting flag_values thuộc tính:

config_setting(
    name = "meaningful_condition_name",
    flag_values = {
        "//myflags:flag1": "value1",
        "//myflags:flag2": "value2",
        ...
    },
)

Hành vi tương tự như đối với cờ tích hợp. Hãy xem tại đây để biết ví dụ về cách hoạt động.

--define là một cú pháp cũ thay thế cho cờ tuỳ chỉnh (ví dụ: --define foo=bar). Điều này có thể được thể hiện trong thuộc tính values (values = {"define": "foo=bar"}) hoặc Thuộc tính define_values (define_values = {"foo": "bar"}). --define chỉ được hỗ trợ cho thao tác quay lại khả năng tương thích. Ưu tiên sử dụng chế độ cài đặt bản dựng Starlark bất cứ khi nào có thể.

values, flag_valuesdefine_values sẽ đánh giá một cách độc lập. Chiến lược phát hành đĩa đơn config_setting khớp nếu tất cả các giá trị trên tất cả các giá trị đó trùng khớp.

Điều kiện mặc định

Điều kiện tích hợp //conditions:default khớp khi không có điều kiện nào khác kết quả phù hợp.

Do quy tắc "chính xác một lần khớp", một thuộc tính có thể định cấu hình không khớp và không có điều kiện mặc định sẽ phát ra lỗi "no matching conditions". Điều này có thể giúp bảo vệ khỏi các lỗi thầm lặng do các chế độ cài đặt không mong muốn:

# myapp/BUILD

config_setting(
    name = "x86_cpu",
    values = {"cpu": "x86"},
)

cc_library(
    name = "x86_only_lib",
    srcs = select({
        ":x86_cpu": ["lib.cc"],
    }),
)
$ bazel build //myapp:x86_only_lib --cpu=arm
ERROR: Configurable attribute "srcs" doesn't match this configuration (would
a default condition help?).
Conditions checked:
  //myapp:x86_cpu

Để rõ ràng hơn nữa về lỗi, bạn có thể đặt thông báo tuỳ chỉnh bằngselect() no_match_error.

Nền tảng

Mặc dù khả năng chỉ định nhiều cờ trên dòng lệnh cung cấp linh hoạt, nhưng việc đặt riêng từng cấu hình mỗi lần cũng có thể khó khăn mà bạn muốn xây dựng một mục tiêu. Platforms (Nền tảng) cho phép bạn hợp nhất các tệp này thành các gói đơn giản.

# myapp/BUILD

sh_binary(
    name = "my_rocks",
    srcs = select({
        ":basalt": ["pyroxene.sh"],
        ":marble": ["calcite.sh"],
        "//conditions:default": ["feldspar.sh"],
    }),
)

config_setting(
    name = "basalt",
    constraint_values = [
        ":black",
        ":igneous",
    ],
)

config_setting(
    name = "marble",
    constraint_values = [
        ":white",
        ":metamorphic",
    ],
)

# constraint_setting acts as an enum type, and constraint_value as an enum value.
constraint_setting(name = "color")
constraint_value(name = "black", constraint_setting = "color")
constraint_value(name = "white", constraint_setting = "color")
constraint_setting(name = "texture")
constraint_value(name = "smooth", constraint_setting = "texture")
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")

platform(
    name = "basalt_platform",
    constraint_values = [
        ":black",
        ":igneous",
    ],
)

platform(
    name = "marble_platform",
    constraint_values = [
        ":white",
        ":smooth",
        ":metamorphic",
    ],
)

Nền tảng này có thể được chỉ định qua dòng lệnh. Việc này sẽ kích hoạt config_setting chứa một tập hợp con constraint_values của nền tảng, cho phép các config_setting đó khớp trong biểu thức select().

Ví dụ: để đặt thuộc tính srcs của my_rocks thành calcite.sh, bạn chỉ cần chạy

bazel build //my_app:my_rocks --platforms=//myapp:marble_platform

Nếu không có nền tảng, mã này có thể có dạng như sau

bazel build //my_app:my_rocks --define color=white --define texture=smooth --define type=metamorphic

select() cũng có thể trực tiếp đọc constraint_value:

constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
sh_binary(
    name = "my_rocks",
    srcs = select({
        ":igneous": ["igneous.sh"],
        ":metamorphic" ["metamorphic.sh"],
    }),
)

Việc này sẽ giúp bạn tiết kiệm nhu cầu về config_setting nguyên mẫu khi chỉ cần hãy kiểm tra các giá trị đơn lẻ.

Các nền tảng vẫn đang trong quá trình phát triển. Hãy xem tài liệu để biết thông tin chi tiết.

Kết hợp select()

select có thể xuất hiện nhiều lần trong cùng một thuộc tính:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"] +
           select({
               ":armeabi_mode": ["armeabi_src.sh"],
               ":x86_mode": ["x86_src.sh"],
           }) +
           select({
               ":opt_mode": ["opt_extras.sh"],
               ":dbg_mode": ["dbg_extras.sh"],
           }),
)

select không được xuất hiện bên trong một select khác. Nếu bạn cần lồng selects còn thuộc tính của bạn lấy mục tiêu khác làm giá trị, hãy sử dụng mục tiêu trung gian:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":armeabi_mode": [":armeabi_lib"],
        ...
    }),
)

sh_library(
    name = "armeabi_lib",
    srcs = select({
        ":opt_mode": ["armeabi_with_opt.sh"],
        ...
    }),
)

Nếu bạn cần select để khớp khi nhiều điều kiện khớp, hãy cân nhắc sử dụng phương thức AND tạo chuỗi.

Xâu chuỗi OR

Hãy cân nhắc thực hiện những bước sau:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": [":standard_lib"],
        ":config2": [":standard_lib"],
        ":config3": [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

Hầu hết các điều kiện đều đánh giá đến cùng một phần phụ thuộc. Nhưng cú pháp này khó đọc và duy trì. Sẽ rất tuyệt nếu bạn không phải lặp lại [":standard_lib"] nhiều lần.

Một lựa chọn là xác định trước giá trị dưới dạng biến BUILD:

STANDARD_DEP = [":standard_lib"]

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": STANDARD_DEP,
        ":config2": STANDARD_DEP,
        ":config3": STANDARD_DEP,
        ":config4": [":special_lib"],
    }),
)

Thao tác này giúp bạn quản lý phần phụ thuộc dễ dàng hơn. Nhưng nó vẫn dẫn đến việc không cần thiết trùng lặp.

Để được hỗ trợ trực tiếp hơn, vui lòng sử dụng một trong các cách sau:

selects.with_or

Macro with_or trong mô-đun selects của Skylib hỗ trợ các điều kiện ORing ngay bên trong select:

load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = selects.with_or({
        (":config1", ":config2", ":config3"): [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

selects.config_setting_group

Macro config_setting_group trong mô-đun selects của Skylib hỗ trợ OR nhiều config_setting:

load("@bazel_skylib//lib:selects.bzl", "selects")
config_setting(
    name = "config1",
    values = {"cpu": "arm"},
)
config_setting(
    name = "config2",
    values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
    name = "config1_or_2",
    match_any = [":config1", ":config2"],
)
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1_or_2": [":standard_lib"],
        "//conditions:default": [":other_lib"],
    }),
)

Không giống như selects.with_or, các mục tiêu khác nhau có thể chia sẻ :config1_or_2 trên các thuộc tính khác nhau.

Lỗi nhiều điều kiện khớp với nhau, trừ phi một điều kiện là "chuyên biệt" rõ ràng của các điều kiện khác hoặc tất cả đều phân giải thành cùng một giá trị. Xem tại đây để biết chi tiết.

VÀ tạo chuỗi

Nếu bạn cần một nhánh select để so khớp khi nhiều điều kiện khớp, hãy sử dụng macro Skylib config_setting_group:

config_setting(
    name = "config1",
    values = {"cpu": "arm"},
)
config_setting(
    name = "config2",
    values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
    name = "config1_and_2",
    match_all = [":config1", ":config2"],
)
sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1_and_2": [":standard_lib"],
        "//conditions:default": [":other_lib"],
    }),
)

Không giống như việc nối OR, bạn không thể trực tiếp AND các config_setting hiện có bên trong select. Bạn phải gói chúng một cách rõ ràng trong một config_setting_group.

Thông báo lỗi tuỳ chỉnh

Theo mặc định, khi không có điều kiện nào khớp, mục tiêu mà select() được đính kèm sẽ không thành công với lỗi:

ERROR: Configurable attribute "deps" doesn't match this configuration (would
a default condition help?).
Conditions checked:
  //tools/cc_target_os:darwin
  //tools/cc_target_os:android

Bạn có thể tuỳ chỉnh thuộc tính này bằng thuộc tính no_match_error:

cc_library(
    name = "my_lib",
    deps = select(
        {
            "//tools/cc_target_os:android": [":android_deps"],
            "//tools/cc_target_os:windows": [":windows_deps"],
        },
        no_match_error = "Please build with an Android or Windows toolchain",
    ),
)
$ bazel build //myapp:my_lib
ERROR: Configurable attribute "deps" doesn't match this configuration: Please
build with an Android or Windows toolchain

Khả năng tương thích của quy tắc

Các hoạt động triển khai quy tắc sẽ nhận được giá trị đã phân giải của các thuộc tính có thể định cấu hình. Ví dụ: giả sử:

# myapp/BUILD

some_rule(
    name = "my_target",
    some_attr = select({
        ":foo_mode": [":foo"],
        ":bar_mode": [":bar"],
    }),
)
$ bazel build //myapp/my_target --define mode=foo

Mã triển khai quy tắc thấy ctx.attr.some_attr dưới dạng [":foo"].

Macro có thể chấp nhận mệnh đề select() và truyền các mệnh đề đó đến các quy tắc gốc. Tuy nhiên, họ không thể trực tiếp thao túng các hoạt động đó. Ví dụ: không có cách nào để một macro chuyển đổi

select({"foo": "val"}, ...)

tới

select({"foo": "val_with_suffix"}, ...)

Có hai lý do cho việc này.

Trước tiên, các macro cần biết đường dẫn mà select sẽ chọn không thể hoạt động vì macro được đánh giá trong giai đoạn tải của Bazel, xảy ra trước khi xác định được các giá trị gắn cờ. Đây là một hạn chế chính trong thiết kế của Bazel và có thể sẽ sớm thay đổi.

Thứ hai, các macro chỉ cần lặp lại trên tất cả đường dẫn select, trong khi khả thi về mặt kỹ thuật, thiếu giao diện người dùng mạch lạc. Bạn cần thiết kế thêm để thay đổi điều này.

Truy vấn Bazel và cquery

Bazel query hoạt động trong giai đoạn tải của Bazel. Điều này có nghĩa là công cụ này không biết mục tiêu sử dụng cờ dòng lệnh nào vì các cờ đó sẽ không được đánh giá cho đến sau này trong bản dựng (trong giai đoạn phân tích). Vì vậy, công cụ này không thể xác định nhánh select() nào được chọn.

Bazel cquery hoạt động sau giai đoạn phân tích của Bazel, vì vậy, công cụ này có tất cả thông tin này và có thể phân giải chính xác select().

Hãy cân nhắc:

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# myapp/BUILD

string_flag(
    name = "dog_type",
    build_setting_default = "cat"
)

cc_library(
    name = "my_lib",
    deps = select({
        ":long": [":foo_dep"],
        ":short": [":bar_dep"],
    }),
)

config_setting(
    name = "long",
    flag_values = {":dog_type": "dachshund"},
)

config_setting(
    name = "short",
    flag_values = {":dog_type": "pug"},
)

query ước lượng quá mức các phần phụ thuộc của :my_lib:

$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep

trong khi cquery cho thấy các phần phụ thuộc chính xác:

$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep

Câu hỏi thường gặp

Tại sao select() không hoạt động trong macro?

Select() hoạt động trong quy tắc! Xem phần Khả năng tương thích với quy tắc để chi tiết.

Câu hỏi này thường có nghĩa là vấn đề chính là select() không hoạt động trong macro. Những quy tắc này khác với quy tắc. Xem tài liệu về quy tắcmacro để hiểu sự khác biệt. Dưới đây là ví dụ toàn diện:

Xác định quy tắc và macro:

# myapp/defs.bzl

# Rule implementation: when an attribute is read, all select()s have already
# been resolved. So it looks like a plain old attribute just like any other.
def _impl(ctx):
    name = ctx.attr.name
    allcaps = ctx.attr.my_config_string.upper()  # This works fine on all values.
    print("My name is " + name + " with custom message: " + allcaps)

# Rule declaration:
my_custom_bazel_rule = rule(
    implementation = _impl,
    attrs = {"my_config_string": attr.string()},
)

# Macro declaration:
def my_custom_bazel_macro(name, my_config_string):
    allcaps = my_config_string.upper()  # This line won't work with select(s).
    print("My name is " + name + " with custom message: " + allcaps)

Tạo bản sao cho quy tắc và macro:

# myapp/BUILD

load("//myapp:defs.bzl", "my_custom_bazel_rule")
load("//myapp:defs.bzl", "my_custom_bazel_macro")

my_custom_bazel_rule(
    name = "happy_rule",
    my_config_string = select({
        "//tools/target_cpu:x86": "first string",
        "//third_party/bazel_platforms/cpu:ppc": "second string",
    }),
)

my_custom_bazel_macro(
    name = "happy_macro",
    my_config_string = "fixed string",
)

my_custom_bazel_macro(
    name = "sad_macro",
    my_config_string = select({
        "//tools/target_cpu:x86": "first string",
        "//third_party/bazel_platforms/cpu:ppc": "other string",
    }),
)

Không tạo được bản dựng vì sad_macro không thể xử lý select():

$ bazel build //myapp:all
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
  (most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
  my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
ERROR: error loading package 'myapp': Package 'myapp' contains errors.

Quá trình tạo sẽ thành công khi bạn chú thích sad_macro:

# Comment out sad_macro so it doesn't mess up the build.
$ bazel build //myapp:all
DEBUG: /myworkspace/myapp/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING.
DEBUG: /myworkspace/myapp/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING.

Bạn không thể thay đổi chế độ này vì macro theo định nghĩa được đánh giá trước Bazel đọc cờ dòng lệnh của bản dựng. Điều đó có nghĩa là không có đủ để đánh giá các phương thức Select().

Tuy nhiên, macro có thể truyền select() dưới dạng blob mờ đến các quy tắc:

# myapp/defs.bzl

def my_custom_bazel_macro(name, my_config_string):
    print("Invoking macro " + name)
    my_custom_bazel_rule(
        name = name + "_as_target",
        my_config_string = my_config_string,
    )
$ bazel build //myapp:sad_macro_less_sad
DEBUG: /myworkspace/myapp/defs.bzl:23:3: Invoking macro sad_macro_less_sad.
DEBUG: /myworkspace/myapp/defs.bzl:15:3: My name is sad_macro_less_sad with custom message: FIRST STRING.

Tại sao Select() luôn trả về giá trị true?

Theo định nghĩa, macro (nhưng không phải quy tắc) không thể đánh giá select(), nên mọi nỗ lực đánh giá select() thường sẽ dẫn đến lỗi:

ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
  (most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
  my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().

Boolean là một trường hợp đặc biệt không thành công một cách âm thầm, do đó bạn nên đặc biệt cảnh giác với họ:

$ cat myapp/defs.bzl
def my_boolean_macro(boolval):
  print("TRUE" if boolval else "FALSE")

$ cat myapp/BUILD
load("//myapp:defs.bzl", "my_boolean_macro")
my_boolean_macro(
    boolval = select({
        "//tools/target_cpu:x86": True,
        "//third_party/bazel_platforms/cpu:ppc": False,
    }),
)

$ bazel build //myapp:all --cpu=x86
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
$ bazel build //mypro:all --cpu=ppc
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.

Điều này xảy ra vì macro không hiểu được nội dung của select(). Vì vậy, điều họ thực sự đánh giá là chính đối tượng select(). Theo Thiết kế Pythonic tất cả các đối tượng trừ một số rất ít ngoại lệ tự động trả về giá trị true.

Tôi có thể đọc Select() như một lệnh chính tả không?

Macro không thể đánh giá(các) lựa chọn vì macro đánh giá trước Bazel biết các tham số dòng lệnh của bản dựng là gì. Ít nhất thì họ có đọc được từ điển của select() chẳng hạn để thêm hậu tố cho mỗi giá trị?

Về mặt lý thuyết, điều này có thể thực hiện được, nhưng chưa phải là tính năng của Bazel. Việc bạn có thể làm hôm nay là chuẩn bị một từ điển thẳng, sau đó đưa từ điển đó vào select():

$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
  for key in select_cmd.keys():
    select_cmd[key] += " WITH SUFFIX"
  native.genrule(
      name = name,
      outs = [name + ".out"],
      srcs = [],
      cmd = "echo " + select(select_cmd + {"//conditions:default": "default"})
        + " > $@"
  )

$ cat myapp/BUILD
selecty_genrule(
    name = "selecty",
    select_cmd = {
        "//tools/target_cpu:x86": "x86 mode",
    },
)

$ bazel build //testapp:selecty --cpu=x86 && cat bazel-genfiles/testapp/selecty.out
x86 mode WITH SUFFIX

Nếu muốn hỗ trợ cả select() và kiểu gốc, thì bạn có thể làm như sau:

$ cat myapp/defs.bzl
def selecty_genrule(name, select_cmd):
    cmd_suffix = ""
    if type(select_cmd) == "string":
        cmd_suffix = select_cmd + " WITH SUFFIX"
    elif type(select_cmd) == "dict":
        for key in select_cmd.keys():
            select_cmd[key] += " WITH SUFFIX"
        cmd_suffix = select(select_cmd + {"//conditions:default": "default"})

    native.genrule(
        name = name,
        outs = [name + ".out"],
        srcs = [],
        cmd = "echo " + cmd_suffix + "> $@",
    )

Tại sao Select() không hoạt động với binding()?

bind() là quy tắc WORKSPACE, không phải là quy tắc BUILD.

Quy tắc Workspace không có cấu hình cụ thể và không được đánh giá theo cách tương tự như quy tắc BUILD. Do đó, select() trong bind() không thể đánh giá thực tế cho bất kỳ nhánh cụ thể nào.

Thay vào đó, bạn nên sử dụng alias(), với select() trong thuộc tính actual, để thực hiện loại xác định thời gian chạy này. Chiến dịch này hoạt động đúng cách vì alias() là quy tắc BUILD và được đánh giá bằng cấu hình cụ thể.

Bạn thậm chí có thể đặt mục tiêu bind() trỏ đến alias() nếu cần.

$ cat WORKSPACE
workspace(name = "myapp")
bind(name = "openssl", actual = "//:ssl")
http_archive(name = "alternative", ...)
http_archive(name = "boringssl", ...)

$ cat BUILD
config_setting(
    name = "alt_ssl",
    define_values = {
        "ssl_library": "alternative",
    },
)

alias(
    name = "ssl",
    actual = select({
        "//:alt_ssl": "@alternative//:ssl",
        "//conditions:default": "@boringssl//:ssl",
    }),
)

Với chế độ thiết lập này, bạn có thể truyền --define ssl_library=alternative và mọi mục tiêu phụ thuộc vào //:ssl hoặc //external:ssl sẽ thấy mục thay thế nằm tại @alternative//:ssl.

Tại sao select() không chọn nội dung mà tôi mong đợi?

Nếu //myapp:fooselect() không chọn điều kiện mà bạn mong đợi, hãy sử dụng cquerybazel config để gỡ lỗi:

Nếu //myapp:foo là mục tiêu cấp cao nhất mà bạn đang tạo, hãy chạy:

$ bazel cquery //myapp:foo <desired build flags>
//myapp:foo (12e23b9a2b534a)

Nếu bạn đang tạo một số //bar mục tiêu khác phụ thuộc vào //myapp:foo ở đâu đó trong đồ thị con của nó, hãy chạy:

$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar   (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)

(12e23b9a2b534a) bên cạnh //myapp:foo là một hàm băm của để phân giải select() của //myapp:foo. Bạn có thể kiểm tra các giá trị của lớp này bằng bazel config:

$ bazel config 12e23b9a2b534a
BuildConfigurationValue 12e23b9a2b534a
Fragment com.google.devtools.build.lib.analysis.config.CoreOptions {
  cpu: darwin
  compilation_mode: fastbuild
  ...
}
Fragment com.google.devtools.build.lib.rules.cpp.CppOptions {
  linkopt: [-Dfoo=bar]
  ...
}
...

Sau đó, hãy so sánh kết quả này với chế độ cài đặt dự kiến của mỗi config_setting.

//myapp:foo có thể tồn tại ở nhiều cấu hình trong cùng một bản dựng. Hãy xem tài liệu về cquery để biết hướng dẫn về cách sử dụng somepath nhằm chọn đúng hàm.

Tại sao select() không hoạt động với các nền tảng?

Bazel không hỗ trợ các thuộc tính có thể định cấu hình để kiểm tra xem một nền tảng cụ thể có được hỗ trợ hay không là nền tảng mục tiêu vì ngữ nghĩa không rõ ràng.

Ví dụ:

platform(
    name = "x86_linux_platform",
    constraint_values = [
        "@platforms//cpu:x86",
        "@platforms//os:linux",
    ],
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":x86_linux_platform": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

Trong tệp BUILD này, bạn nên dùng select() nếu nền tảng mục tiêu có cả Có hạn chế đối với @platforms//cpu:x86@platforms//os:linux, nhưng không phải:x86_linux_platform được xác định ở đây? Tác giả của tệp BUILD và người dùng người xác định nền tảng riêng biệt có thể có các ý tưởng khác nhau.

Tôi nên làm gì thay vào đó?

Thay vào đó, hãy xác định config_setting khớp với bất kỳ nền tảng nào có những ràng buộc sau:

config_setting(
    name = "is_x86_linux",
    constraint_values = [
        "@platforms//cpu:x86",
        "@platforms//os:linux",
    ],
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":is_x86_linux": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

Quy trình này xác định các ngữ nghĩa cụ thể, giúp người dùng hiểu rõ hơn những gì nền tảng đáp ứng các điều kiện mong muốn.

Nếu tôi thực sự muốn select trên nền tảng thì sao?

Nếu các yêu cầu về bản dựng của bạn đòi hỏi cụ thể việc kiểm tra nền tảng, bạn có thể lật giá trị của cờ --platforms trong config_setting:

config_setting(
    name = "is_specific_x86_linux_platform",
    values = {
        "platforms": ["//package:x86_linux_platform"],
    },
)

cc_library(
    name = "lib",
    srcs = [...],
    linkopts = select({
        ":is_specific_x86_linux_platform": ["--enable_x86_optimizations"],
        "//conditions:default": [],
    }),
)

Nhóm Bazel không ủng hộ việc này; việc này sẽ hạn chế quá mức bản dựng và gây nhầm lẫn cho người dùng khi điều kiện dự kiến không khớp.