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

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

Thuộc tính có thể định cấu hình, thường được gọi là select(), là một tính năng của Bazel cho phép người dùng bật/tắt các giá trị của thuộc tính quy tắc xây dựng tại dòng lệnh.

Ví dụ: bạn có thể 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 mà bạn có thể tuỳ chỉnh tại thời gian xây 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 deps dựa trên cờ tại dòng lệnh. Cụ thể, deps trở thành:

Lệnh deps =
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à những nhãn tham chiếu đến các 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 các giá trị khác nhau một cách hiệu quả khi các điều kiện khác nhau được đáp ứng.

Các kết quả trùng khớp phải rõ ràng: nếu nhiều điều kiện trùng khớp thì một trong hai trường hợp sau sẽ xảy ra: * Tất cả đều phân giải thành cùng một giá trị. Ví dụ: khi chạy trên linux x86, {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} này không mơ hồ vì cả hai nhánh đều phân giải thành "hello". * values của một người là tập hợp chứa thực sự của tất cả những người khác. Ví dụ: values = {"cpu": "x86", "compilation_mode": "dbg"} là một chuyên môn hoá 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.

Mặc dù ví dụ này sử dụng deps, nhưng select() cũng hoạt động hiệu quả trên srcs, resources, cmd và hầu hết các thuộc tính khác. Chỉ có một số ít thuộc tính là không thể định cấu hình và những thuộc tính này được chú thích rõ ràng. Ví dụ: thuộc tính values riêng 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 sẽ thay đổi các tham số bản dựng cho tất cả các phần phụ thuộc bắc cầu trong một mục tiêu. Ví dụ: genrule của tools sẽ thay đổi --cpu thành CPU của máy chạy Bazel (nhờ quá trình 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à quá trình 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ột máy nhà phát triển x86 sẽ liên kết bản dựng với g_arm.src, tool1x86tool.cc. Cả hai select được đí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 thành x86 cho tool1 và các phần phụ thuộc bắc cầu của nó. select trên 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 một thuộc tính có thể định cấu hình là một giá trị tham chiếu nhãn đến 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 đóng gói các điều kiện này trong một mục tiêu, bạn có thể dễ dàng duy trì các điều kiện "tiêu chuẩn" mà người dùng có thể tham chiếu 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ụ này hiểu rõ các cờ này cho mọi bản dựng trong tất cả dự án. Các thuộc tính này được chỉ định bằng thuộc tính config_setting của values:

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

flagN là tên cờ (không có --, nên "cpu" thay vì "--cpu"). valueN là giá trị dự kiến cho cờ đó. :meaningful_condition_name sẽ khớp nếu mọi mục trong values đều khớp. Không cần quan tâm đến thứ tự.

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 mục tiêu. Ví dụ: --show_progress không được phép vì tham số này 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 thể sử dụng cờ đó để tạo kết quả. Không có tài liệu nào mô tả chính xác bộ cờ được hỗ trợ. Trong thực tế, hầu hết các cờ "hợp lý" đều hoạt động.

Cờ tuỳ chỉnh

Bạn có thể mô hình hoá 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ác cờ tích hợp, các cờ này được xác định là mục tiêu xây dựng, vì vậy Bazel tham chiếu chúng bằng nhãn mục tiêu.

Các sự kiện này được kích hoạt bằng thuộc tính flag_values của config_setting:

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

Hành vi này giống như đối với cờ tích hợp. Hãy xem ví dụ hoạt động tại đây.

--define là một cú pháp cũ thay thế cho cờ tuỳ chỉnh (ví dụ: --define foo=bar). Bạn có thể biểu thị cú pháp này 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ợ để tương thích ngược. Ưu tiên dùng chế độ cài đặt bản dựng Starlark bất cứ khi nào có thể.

values, flag_valuesdefine_values đánh giá độc lập. config_setting sẽ khớp nếu tất cả giá trị trên tất cả các giá trị đó đều khớp.

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

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

Do quy tắc "chỉ có một kết quả khớp", nên một thuộc tính có thể định cấu hình mà không có kết quả 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ể ngăn chặn các lỗi âm thầm do 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

Để có lỗi rõ ràng hơn, bạn có thể đặt thông báo tuỳ chỉnh bằng thuộc tính no_match_error của select().

Nền tảng

Mặc dù khả năng chỉ định nhiều cờ trên dòng lệnh mang lại sự linh hoạt, nhưng việc đặt riêng từng cờ mỗi khi bạn muốn tạo một mục tiêu cũng có thể gây phiền toái. Nền tảng cho phép bạn hợp nhất những nội dung 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",
    ],
)

Bạn có thể chỉ định nền tảng trên dòng lệnh. Thao tác này sẽ kích hoạt các config_setting chứa một tập hợp con của constraint_values của nền tảng, cho phép các config_setting đó khớp với các 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, thì có thể sẽ trông như sau

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

select() cũng có thể đọc trực tiếp 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"],
    }),
)

Điều này giúp bạn không cần dùng các config_settings chung khi chỉ cần kiểm tra các giá trị đơn lẻ.

Các nền tảng này 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 thể xuất hiện bên trong một select khác. Nếu bạn cần lồng selects và thuộc tính của bạn lấy các mục tiêu khác làm giá trị, hãy sử dụng một 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 chuỗi AND.

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á cùng một dep. Nhưng cú pháp này khó đọc và duy trì. Sẽ rất tốt nếu bạn không phải lặp lại thao tác [":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"],
    }),
)

Điều này giúp bạn dễ dàng quản lý phần phụ thuộc. Nhưng việc này vẫn gây ra tình trạng trùng lặp không cần thiết.

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

selects.with_or

Macro with_or trong mô-đun selects của Skylib hỗ trợ các điều kiện OR trực tiếp 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ể dùng chung :config1_or_2 trên nhiều thuộc tính.

Sẽ xảy ra lỗi nếu nhiều điều kiện trùng khớp, trừ phi một điều kiện là "chuyên môn hoá" rõ ràng của các điều kiện khác hoặc tất cả các điều kiện đều phân giải thành cùng một giá trị. Hãy xem tại đây để biết thông tin chi tiết.

Xâu chuỗi AND

Nếu bạn cần một nhánh select để so khớp khi nhiều điều kiện trùng 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 liên kết OR, bạn không thể trực tiếp AND các config_setting hiện có bên trong một 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 nội dung 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 phương thức triển khai quy tắc nhận được các giá trị đã phân giải của các thuộc tính có thể định cấu hình. Ví dụ: cho trước:

# 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 xem ctx.attr.some_attr[":foo"].

Các macro có thể chấp nhận mệnh đề select() và chuyển chúng qua các quy tắc gốc. Tuy nhiên, họ không thể thao tác trực tiếp trên các đối tượng này. 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"}, ...)

Điều này là do 2 lý do.

Trước tiên, những 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 các giá trị cờ được biết. Đây là một hạn chế thiết kế cốt lõi của Bazel và khó có thể thay đổi trong thời gian tới.

Thứ hai, các macro chỉ cần lặp lại trên tất cả các đường dẫn select, mặc dù có thể thực hiện về mặt kỹ thuật nhưng lại thiếu giao diện người dùng nhất quán. 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à nó không biết cờ dòng lệnh mà một mục tiêu sử dụng vì những cờ đó không được đánh giá cho đến sau này trong quá trình xây dựng (trong giai đoạn phân tích). Vì vậy, công cụ này không thể xác định được 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, Bazel có tất cả thông tin này và có thể phân giải chính xác cá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 tính 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 của nó:

$ 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 các quy tắc! Hãy xem phần Khả năng tương thích của quy tắc để biết thông tin chi tiết.

Vấn đề chính mà câu hỏi này thường đề cập đến là select() không hoạt động trong macro. Các quy tắc này khác với các quy tắc khác. Hãy xem tài liệu về quy tắcmacro để hiểu rõ sự khác biệt. Sau đây là một 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 thực thể 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 thể tạo vì sad_macro không xử lý được 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.

Không thể thay đổi điều này vì theo định nghĩa, các macro được đánh giá trước khi Bazel đọc cờ dòng lệnh của bản dựng. Điều đó có nghĩa là không có đủ thông tin để đánh giá select()s.

Tuy nhiên, macro có thể truyền select() dưới dạng các 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?

Vì 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 làm như vậy thường sẽ tạo ra 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, vì vậy bạn phải đặc biệt cảnh giác với các trường hợp này:

$ 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 nội dung của select(). Vì vậy, điều mà họ thực sự đánh giá là chính đối tượng select(). Theo các tiêu chuẩn thiết kế Pythonic, tất cả các đối tượng (ngoại trừ một số ít) đều tự động trả về giá trị true.

Tôi có thể đọc select() như một dict không?

Các macro không thể đánh giá select(s) vì các macro đánh giá trước khi Bazel biết các tham số dòng lệnh của bản dựng. Ít nhất, họ có thể đọc từ điển của select() để, ví dụ: thêm một hậu tố vào mỗi giá trị không?

Về mặt khái niệm, điều này là có thể, nhưng đây chưa phải là một tính năng của Bazel. Những việc bạn có thể làm hôm nay là chuẩn bị một từ điển đơn giản, 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ả loại select() và loại gốc, 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 bind()?

Trước hết, không dùng bind(). alias() không được dùng nữa.

Câu trả lời về mặt kỹ thuật là bind() là một quy tắc repo, chứ không phải quy tắc BUILD.

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

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

Bạn thậm chí có thể đặt bind() làm điểm đến của 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 lựa chọn thay thế nằm tại @alternative//:ssl.

Nhưng thực sự thì bạn nên ngừng sử dụng bind().

Tại sao select() không chọn những gì 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 xây dựng, hãy chạy:

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

Nếu bạn đang tạo một mục tiêu //bar khác phụ thuộc vào //myapp:foo ở đâu đó trong đồ thị con của mục tiêu đó, 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 cấu hình phân giải select() của //myapp:foo. Bạn có thể kiểm tra các giá trị của nó 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 đầu ra này với các chế độ cài đặt mà mỗi config_setting mong đợi.

//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 cquery để biết hướng dẫn về cách sử dụng somepath để có được somepath phù hợp.

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 nhất định có phải là nền tảng mục tiêu hay không 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 sử dụng select() nào nếu nền tảng mục tiêu có cả các ràng buộc @platforms//cpu:x86@platforms//os:linux, nhưng không phải là :x86_linux_platform được xác định ở đây? Tác giả của tệp BUILD và người dùng xác định nền tảng riêng biệt có thể có những ý tưởng khác nhau.

Tôi nên làm gì thay vì vậy?

Thay vào đó, hãy xác định một config_setting khớp với bất kỳ nền tảng nào có các điều kiện 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 ngữ nghĩa cụ thể, giúp người dùng hiểu rõ hơn về những 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 này thì sao?

Nếu các yêu cầu về bản dựng của bạn đặc biệt yêu cầu 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 khuyến khích bạn làm việc này; việc này sẽ hạn chế quá mức bản dựng của bạn và khiến người dùng nhầm lẫn khi điều kiện dự kiến không khớp.