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

Báo cáo sự cố Xem nguồn

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 các giá trị của các thuộc tính quy tắc xây dựng ở 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 có thể được tuỳ chỉnh tại thời điểm 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 sẽ khai báo một cc_binary để "chọn" các phần phụ thuộc dựa trên các cờ ở 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 điều kiện cấu hình, đó là các 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 hiệu quả các giá trị khác nhau khi các điều kiện khác nhau được giữ lại.

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 điều kiện đó * Tất cả sẽ giải quyết cùng một giá trị. Ví dụ: khi chạy trên Linux x86, giá trị này không rõ ràng là {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"} vì cả hai nhánh đều phân giải thành "hello". * values của một người là tập mẹ nghiêm ngặt của mọi phần tử 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 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 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ụ: Bạn không thể định cấu hình thuộc tính values của riêng config_setting.

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

Một số thuộc tính nhấ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 trong mục tiêu. Ví dụ: tools của genrule thay đổi --cpu thành CPU của máy chạy Bazel (do quá trình biên dịch chéo, có thể khác với CPU mà mục tiêu được tạo). Quá trình này 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 tính của 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 đính kèm với my_genrule đều sử dụng 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 thuộc tính này. 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 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 đóng gói những đối tượng 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 đa 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ụ bản dựng vốn hiểu được các cờ này cho mọi bản dựng trong mọi dự án. Các đối số 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 "cpu" thay vì "--cpu"). valueN là giá trị dự kiến cho cờ đó. :meaningful_condition_name khớp nếu mọi mục trong values khớp. Đơn đặt hàng 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" } khớp với bazel build -c opt
  • values = { "force_pic": "true" } khớp với bazel build --force_pic=1
  • values = { "force_pic": "0" } khớp với bazel build --noforce_pic

config_setting chỉ hỗ trợ cờ ảnh hưởng đến hành vi 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. Mục tiêu không thể sử dụng cờ đó để tạo kết quả. Tập hợp chính xác cờ được hỗ trợ sẽ không được ghi lại. Trong thực tế, hầu hết các cờ "có ý nghĩa" đều hoạt động.

Cờ tuỳ chỉnh

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

Những cảnh báo 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 tương tự như cờ tích hợp sẵn. Hãy xem ví dụ cách 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ợ cho khả năng tương thích ngược. Ưu tiên các chế độ cài đặt bản dựng Starlark bất cứ khi nào có thể.

values, flag_valuesdefine_values sẽ đánh giá độc lập. config_setting sẽ khớp nếu mọi 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 khớp.

Do quy tắc "khớp chính xác một", thuộc tính có thể định cấu hình không có kết quả trùng khớp và không có điều kiện mặc định nào sẽ phát sinh lỗi "no matching conditions". Điều này có thể giúp tránh các lỗi ngầm trước 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

Để có lỗi rõ ràng hơn nữa, bạn có thể đặt thông điệp 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 thiết lập riêng từng cờ cho mỗi lần bạn muốn tạo mục tiêu cũng có thể gây khó khăn. Nền tảng cho phép bạn hợp nhất những nền tảng 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 qua dòng lệnh. Tính năng này 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 với nhau 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, đoạn mã này có thể trông giống như

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 cá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"],
    }),
)

Điều này giúp giảm nhu cầu sử dụng config_setting nguyên mẫu khi bạn chỉ cần kiểm tra các giá trị duy nhất.

Các nền tảng vẫn đang trong quá trình phát triển. Hãy xem tài liệu này để 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 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 một select khớp khi nhiều điều kiện trùng khớp, hãy cân nhắc việc tạo chuỗi .

Tạo chuỗi HOẶC

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á cho cùng một phần phụ thuộc. Nhưng cú pháp này rất khó đọc và duy trì. Sẽ không cần phải lặp lại [":standard_lib"] nhiều lần.

Có một lựa chọn để 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 này. Nhưng điều này vẫn gây ra sự 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 các nguồn sau:

selects.with_or

Macro with_or trong mô-đun selects của Skylib trực tiếp hỗ trợ các điều kiện OR 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.

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

Tạo chuỗi

Nếu bạn cần một nhánh select để khớp khi nhiều điều kiện khớp với nhau, 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ư tạo chuỗi OR, các config_setting hiện có không thể trực tiếp ANDed bên trong một select. Bạn phải gói rõ ràng chú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 đính kèm select() sẽ bị 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 phần 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

Quá trình triển khai quy tắc sẽ nhận được giá trị đã giải quyết của các thuộc tính có thể định cấu hình. Chẳng hạn như:

# myapp/BUILD

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

Đoạn mã triển khai quy tắc sẽ thấy ctx.attr.some_attr[":foo"].

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

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

tới

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

Điều này là vì hai lý do.

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

Thứ hai, các macro chỉ cần lặp lại trên tất cả đường dẫn select, mặc dù về mặt kỹ thuật khả thi, 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 trạng thái 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à không biết mục tiêu sử dụng cờ nào vì những 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, ứng dụng này không thể xác định những nhánh select() được chọn.

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

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 vượt quá 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 chính xác các phần phụ thuộ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! Hãy xem phần Khả năng tương thích với 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ó nghĩa là select() không hoạt động trong macro. Những quy tắc này khác với quy tắ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({
        "//third_party/bazel_platforms/cpu:x86_32": "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({
        "//third_party/bazel_platforms/cpu:x86_32": "first string",
        "//third_party/bazel_platforms/cpu:ppc": "other string",
    }),
)

Quá trình tạo không thành cô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.

Xây dựng thành công khi bạn nhận xét 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 điều này vì các macro theo định nghĩa đượ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ể chuyển select() dưới dạng mờ 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 (chứ 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ẽ 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 sẽ không hoạt động một cách yên lặng, vì vậy, bạn phải đặc biệt chú ý đến chúng:

$ 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({
        "//third_party/bazel_platforms/cpu:x86_32": 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 do các macro không hiểu được nội dung của select(). Vì vậy, những gì họ thực sự đang đánh giá là chính đối tượng select(). Theo tiêu chuẩn thiết kế Pythonic, tất cả đối tượng ngoại trừ rất ít trường hợp ngoại lệ sẽ tự động trả về giá trị true (đúng).

Tôi có thể đọc Select() như đọc lệnh không?

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

Về mặt lý thuyết, điều này là có thể áp dụng, nhưng tính năng nà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 thẳng, sau đó đưa dữ liệu đó 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 = {
        "//third_party/bazel_platforms/cpu:x86_32": "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, 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, đừng sử dụng bind(). Ngừng sử dụng và thay bằng alias().

Câu trả lời về mặt kỹ thuật cho thấy bind() là quy tắc kho lưu trữ chứ không phải quy tắc XÂY DỰNG.

Các quy tắc Repo không có cấu hình cụ thể và không được đánh giá theo cách giống như quy tắc XÂY DỰNG. Do đó, select() trong bind() không thể thực sự đánh giá 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 để xác định loại thời gian chạy này. Phương thức này hoạt động đúng cách, vì alias() là quy tắc XÂY DỰNG và được đánh giá bằng một cấu hình cụ thể.

Thậm chí, bạn có thể đặt một điểm mục tiêu bind() đế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 cá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 tiêu thay thế nằm tại @alternative//:ssl.

Nhưng thực sự, ngừng sử dụng bind().

Tại sao Select() của tôi không chọn như tôi mong đợi?

Nếu //myapp:fooselect() không chọn điều kiện 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 số mục tiêu //bar khác phụ thuộc vào //myapp:foo ở đâu đó trong đồ thị con, 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 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 đó, 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 các tài liệu về cquery để biết hướng dẫn về cách sử dụng somepath để có được đúng truy vấn.

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, select() nào sẽ được dùng nếu nền tảng mục tiêu có cả điều kiện ràng buộc @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 xác định nền tảng riêng biệt có thể có các ý tưởng khác nhau.

Thay vào đó, tôi nên làm gì?

Thay vào đó, hãy xác định một config_setting phù hợp với bất kỳ nền tảng nào có các quy tắc 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 về nền tảng đáp ứng các điều kiện mong muốn.

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

Nếu yêu cầu cụ thể về bản dựng của bạn đòi hỏi 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 chứng thực việc này vì điều này 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.