Các thuộc tính của bản dựng có thể định cấu hình

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.
Báo cáo sự cố Xem nguồn

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 chuyển đổi giá trị của các thuộc tính quy tắc xây dựng tại dòng lệnh.

Ví dụ: bạn có thể sử dụng tính năng này cho thư viện đa nền tảng tự động triển khai thích hợp cho kiến trúc hoặc cho tệp nhị phân có thể định cấu hình tính năng có thể được tùy 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",
    },
)

Đoạn mã này 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 sẽ 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, 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ử dụng hiệu quả các giá trị khác nhau khi lưu giữ các điều kiện khác nhau.

Kết quả trùng khớp phải rõ ràng: chính xác một điều kiện phải khớp hoặc nếu nhiều điều kiện khớp, thì values của một phải là tập hợp con nghiêm ngặt của tất cả các điều kiện khác. Ví dụ: values = {"cpu": "x86", "compilation_mode": "dbg"} là một chuyên môn values = {"cpu": "x86"} rõ ràng. Điều kiện tích hợp sẵn //conditions:default sẽ tự động khớp khi không có tính năng nào khác phù hợp.

Mặc dù ví dụ này sử dụng deps, nhưng select() cũng hoạt động hiệu quả với srcs, resources, cmd và hầu hết các thuộc tính khác. Chỉ một số thuộc tính 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 nhất định thay đổi các thông số bản dựng cho tất cả phần phụ thuộc bắc cầu trong 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 (do tính năng biên dịch chéo, có thể khác với CPU mà mục tiêu được xây dựng). Đâ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"],
    }),
)

chạy

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

trên máy 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 --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 --cpu=x86.

Các điều kiện về 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 config_setting hoặc constraint_value.

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

constraint_value hỗ trợ hành vi đa nền tảng.

Cờ tích hợp

Những cờ như --cpu được tích hợp vào Bazel: công cụ tạo bản dựng vốn hiểu được các cờ đó cho tất cả bản dựng trong tất cả dự án. Những 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ó --, do đó "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 với nhau. Đơ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ụ: bạn 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 thể sử dụng cờ đó để tạo kết quả. Tập hợp cờ chính xác được hỗ trợ sẽ không được ghi lại. Trong thực tế, hầu hết các cờ đều có ý nghĩa khi hoạt động.

Cờ tuỳ chỉnh

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

Các thuộc tính 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ư đối với các cờ tích hợp sẵn. Xem ví dụ tại đây.

--define là một cú pháp cũ cho các cờ tuỳ chỉnh (ví dụ: --define foo=bar). Bạn có thể biểu diễn cú pháp này trong thuộc tính giá trị (values = {"define": "foo=bar"}) hoặc thuộc tính định nghĩa giá trị (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 tất cả giá trị trên tất cả đều khớp.

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

Điều kiện tích hợp sẵn //conditions:default sẽ 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 kết quả", 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 ra lỗi "no matching conditions". Tính năng này có thể bảo vệ khỏi các lỗi không có tiếng trong 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

Đối với những 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().

Các 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, bạn cũng có thể gặp khó khăn khi đặt từng cờ riêng lẻ mỗi khi muốn tạo mục tiêu. Nền tảng cho phép bạn hợp nhất các 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",
    ],
)

Nền tảng có thể được chỉ định trên dòng lệnh. Biến 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 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, trang này có thể trông giố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 phải tạo nguyên mẫu config_setting khi chỉ cần kiểm tra các giá trị duy nhất.

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 được 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ụ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 có nhiều điều kiện khớp, hãy xem xét VÀ tạo chuỗi.

HOẶC chuỗi

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 có giá trị giống nhau. Tuy nhiên, cú pháp này rất khó đọc và duy trì. Tốt nhất là bạn không nên lặp lại [":standard_lib"] nhiều lần.

Một cách để xác định trước giá trị là 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"],
    }),
)

Việc này giúp bạn dễ dàng quản lý phần phụ thuộc hơn. Nhưng nó 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 công cụ 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 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 xảy ra khi nhiều điều kiện cần so khớp, trừ phi một điều kiện là "chuyên môn" rõ ràng. Xem tại đây để biết thông tin chi tiết.

VÀ 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 macro 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ư chuỗi OR, config_setting hiện có không thể được ANDtrực tiếp bên trong một select. Bạn phải đặt các lớp này một cách rõ ràng trong config_setting_group.

Thông báo lỗi tùy chỉnh

Theo mặc định, khi không có điều kiện nào khớp, mục tiêu select() sẽ được đính kèm không thành công kèm theo 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 với quy tắc

Các phương thức 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ụ:

# 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[":foo"].

Macro có thể chấp nhận mệnh đề select() và chuyển chúng đến các quy tắc gốc. Nhưng chúng không thể thao túng trực tiếp chúng. 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, macro cần biết đường dẫn nào select sẽ chọn không thể hoạt động vì macro được đánh giá trong giai đoạn tải của Bazel. Giai đoạn này xảy ra trước khi xác định các giá trị cờ. Đây là hạn chế thiết kế cốt lõi 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 là không có 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à truy vấn

Bazel query hoạt động trong giai đoạn tải của Bazel. Điều này có nghĩa là thuộc tính này không biết cờ nào mà lệnh mà một mục tiêu sử dụng vì các cờ đó không được đánh giá cho đến sau trong bản dựng (trong giai đoạn phân tích). Do đó, hệ thống 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ể giải quyết 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 vượt quá 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 hiển thị 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() không hoạt động trong các quy tắc! Hãy xem bài viết Khả năng tương thích với các quy tắc để biết thông tin chi tiết.

Vấn đề chính mà câu hỏi này thường gặp phải là Select() không hoạt động trong macro. Các 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 sự khác biệt. Đâ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 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",
        "//tools/target_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",
        "//tools/target_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.

Việc tạo bản dựng sẽ 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 macro này vì macro theo định nghĩa được đánh giá trước khi Biazel đọc cờ hiệu dòng lệnh của bản dựng. Điều đó có nghĩa là chúng tôi không có đủ thông tin để đánh giá một số lựa chọn (()).

Tuy nhiên, macro có thể chuyển select() dưới dạng blob mờ sang 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ề true?

Bởi vì macro (nhưng không phải quy tắc) theo định nghĩa không thể đánh giá select(), nên bất kỳ nỗ lực nào xảy ra đều thường gây 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 hoạt động, vì vậy, bạn nên đặc biệt chú ý đến các giá trị 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,
        "//tools/target_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, những gì họ thực sự đánh giá chính là đối tượng select(). Theo tiêu chuẩn thiết kế Pythonic, tất cả đối tượng ngoài một số rất nhỏ các trường hợp ngoại lệ đều tự động trả về giá trị true.

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

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

Về mặt lý thuyết, điều này vẫn có thể thực hiện được, nhưng đó chưa phải là một 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ả loại select() và loại gốc, bạn có thể thực hiện 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 chứ không phải quy tắc BUILD.

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

Bạn thậm chí có thể có một điểm mục tiêu bind()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ể chuyể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 phương án thay thế nằm tại @alternative//:ssl.

Tại sao selected() của tôi 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 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 ở nơi nào đó trong biểu đồ con, hãy chạy như sau:

$ 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 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 biến 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 từng config_setting.

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

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 dùng select() nào nếu nền tảng mục tiêu 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ó các ý tưởng khác nhau.

Tôi nên làm gì?

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 hạn chế 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": [],
    }),
)

Quá trình này xác định ngữ nghĩa cụ thể, giúp người dùng thấy rõ hơn những nền tảng nào đá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 này thì sao?

Nếu các yêu cầu về bản dựng đặ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 xác nhận việc này. Điều này ràng buộc 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.