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 các giá trị
của thuộc tính quy tắc xây dựng tại dòng lệnh.
Bạn có thể dùng tính năng này, chẳng hạn như 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 đ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 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 sẽ trở thành:
| Ra 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
điều kiện cấu hình, là các nhãn tham chiếu đến config_setting
mục tiêu. 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 khi các điều kiện khác nhau được đáp ứng.
Các kết quả so khớp phải không mơ hồ: nếu nhiều điều kiện so khớp thì
* 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, kết quả này không mơ hồ
{"@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 nhánh là tập hợp chứa thực sự của tất cả các nhánh khác. Ví dụ: values = {"cpu": "x86", "compilation_mode": "dbg"}
là một trường hợp đặc biệt không mơ hồ 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 so khớp.
Mặc dù ví dụ này sử dụng deps, select() cũng hoạt động tốt 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
là 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 riêng
config_setting là 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ố xây 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ụ: tools của genrule sẽ thay đổi --cpu thành CPU của máy đang 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 xây dựng). Đây được gọi là a
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 dành cho nhà phát triển x86 sẽ liên kết bản dựng với g_arm.src, tool1, và
x86tool.cc. Cả hai select được đính kèm vào my_genrule đều sử dụng các tham số xây dựng của my_genrule, bao gồm --cpu=arm. Thuộc tính tools sẽ 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ố xây dựng của tool1, bao gồm --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 các chế độ cài đặt 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ụ xây dựng hiểu các cờ này một cách tự nhiên cho tất cả các bản dựng trong tất cả các dự án. Các cờ này được chỉ định bằng thuộc tính
config_setting's
values của:
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 sẽ so khớp nếu mọi mục trong values đều so 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" }so khớp vớibazel build -c optvalues = { "force_pic": "true" }so khớp vớibazel build --force_pic=1values = { "force_pic": "0" }so khớp vớibazel 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ụ:
--show_progress không được phép vì
cờ 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ả. Tập hợp chính xác các cờ được hỗ trợ 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ể 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 sẽ tham chiếu các cờ này bằng nhãn mục tiêu.
Các cờ này được kích hoạt bằng thuộc tính config_setting's
flag_values:
config_setting(
name = "meaningful_condition_name",
flag_values = {
"//myflags:flag1": "value1",
"//myflags:flag2": "value2",
...
},
)
Hành vi giống như đối với các 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ác 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. Hãy ưu tiên chế độ cài đặt bản dựng Starlark bất cứ khi nào có thể.
values, flag_values và define_values đánh giá độc lập. config_setting sẽ so khớp nếu tất cả các giá trị trên tất cả các cờ này đều so khớp.
Điều kiện mặc định
Điều kiện tích hợp //conditions:default sẽ so khớp khi không có điều kiện nào khác so khớp.
Do quy tắc "chỉ có một kết quả so khớp", một thuộc tính có thể định cấu hình không có kết quả so 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 â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 nữa, bạn có thể đặt thông báo tuỳ chỉnh bằng thuộc tính select()'s
no_match_error.
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 xây dựng một 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 các cờ 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. Nền tảng này sẽ kích hoạt các 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 đó so khớp trong 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, thao tác 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"],
}),
)
Điều này giúp bạn không cần sử dụng các config_setting mẫu khi chỉ cần kiểm tra các giá trị riêng lẻ.
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 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ụ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 so khớp khi nhiều điều kiện so khớp, hãy cân nhắc việc xâu 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á thành cùng một phần phụ thuộc. Tuy nhiên, cú pháp này khó đọc và duy trì. Bạn không nên lặp lại [":standard_lib"] nhiều
lần.
Một lựa chọn là xác định trước giá trị làm 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 hơn. Tuy nhiên, thao tá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
Tiện ích
with_or
macro trong mô-đun Skylib's
selects
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
Tiện ích
config_setting_group
macro trong Skylib's
selects
module hỗ trợ OR nhiều config_settings:
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 các thuộc tính khác nhau.
Sẽ xảy ra lỗi nếu nhiều điều kiện so khớp, trừ phi một điều kiện là "trường hợp đặc biệt" không mơ hồ 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ị. 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 so khớp, hãy sử dụng tiện ích
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ư xâu chuỗ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 các `config_setting` đó một cách rõ ràng trong 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 so 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 lỗi 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
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:
# 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 sẽ thấy ctx.attr.some_attr là [":foo"].
Tiện ích có thể chấp nhận các mệnh đề select() và chuyển các mệnh đề đó đến các quy tắc gốc. Tuy nhiên, tiện ích không thể trực tiếp thao tác các mệnh đề đó. Ví dụ: tiện ích không thể 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, các tiện ích cần biết đường dẫn mà select sẽ chọn không thể hoạt động
vì các tiện ích được đánh giá trong giai đoạn tải của Bazel,
giai đoạn này xảy ra trước khi các giá trị cờ được biết.
Đây là một hạn chế cốt lõi trong thiết kế của Bazel và khó có thể thay đổi trong thời gian tới.
Thứ hai, các tiện ích chỉ cần lặp lại trên tất cả các đường dẫn select. Mặc dù về mặt kỹ thuật là có thể, nhưng các tiện ích này 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 nào mà mục tiêu sử dụng vì những
cờ đó không được đánh giá cho đến giai đoạn sau trong bản dựng (trong
giai đoạn phân tích).
Vì vậy, `query` 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, Bazel có
tất cả thông tin này và có thể phân giải select() một cách chính xác.
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á cao 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 hiển thị các phần phụ thuộc chính xác của `:my_lib`:
$ 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 các tiện ích?
select() có 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 là select() không hoạt động trong các tiện ích. Các tiện ích này khác với các quy tắc. Hãy xem tài liệu về các quy tắc và tiện ích để hiểu rõ sự khác biệt. Sau đây là ví dụ toàn diện:
Xác định quy tắc và tiện ích:
# 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)
Khởi tạo quy tắc và tiện ích:
# 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 xây dựng 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.
Quá trình xây 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 điều này vì theo định nghĩa , các tiện ích được đánh giá trước khi Bazel đọc cá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á các select().
Tuy nhiên, các tiện ích có thể chuyển các 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ề true?
Vì các tiện ích (nhưng không phải các quy tắc) theo định nghĩa
không thể đánh giá các select()s, nên mọi nỗ lực thực hiện việc này
thường 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().
Giá trị 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 cần đặc biệt thận trọng với 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({
"//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 vì các tiện ích không hiểu nội dung của select().
Vì vậy, những gì các tiện ích 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 trường hợp ngoại lệ
sẽ tự động trả về true.
Tôi có thể đọc select() như một dict không?
Các tiện ích không thể đánh giá các select() vì các tiện ích đánh giá trước khi
Bazel biết các tham số dòng lệnh của bản dựng. Các tiện ích có thể đọc từ điển của select() để, ví dụ: thêm hậu tố vào từng 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 gì bạn có thể làm ngay 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 = {
"//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 bạn muốn hỗ trợ cả select() và các loại gốc, bạn có thể thực hiện việc này:
$ 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(). Quy tắc này đã ngừng hoạt động để chuyển sang alias().
Câu trả lời kỹ thuật là bind() là một quy tắc kho lưu trữ, không phải là quy tắc BUILD.
Các quy tắc 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ể thực sự đá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 select() trong
thuộc tính actual để thực hiện loại 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ể trỏ 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 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 lựa chọn thay thế
nằm tại @alternative//:ssl.
Nhưng thực sự, hãy ngừng sử dụng bind().
Tại sao select() không chọn những gì tôi mong đợi?
Nếu //myapp:foo có select() không chọn điều kiện mà bạn mong đợi,
hãy sử dụng cquery và bazel 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 xây dựng một mục tiêu khác //bar phụ thuộc vào //myapp:foo ở đâu đó trong biểu đồ 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ã 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 mã băm 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 các chế độ cài đặt mà mỗi config_setting mong đợi.
//myapp:foo có thể tồn tại trong 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
cquery để được hướng dẫn về cách sử dụng somepath để lấy đúng
cấu hình.
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 sử dụng nếu nền tảng mục tiêu có cả các ràng buộc @platforms//cpu:x86 và @platforms//os:linux, nhưng không phải là :x86_linux_platform được xác định tại đâ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ó ý tưởng khác nhau.
Tôi nên làm gì?
Thay vào đó, hãy xác định config_setting so khớp với bất kỳ nền tảng nào có cá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": [],
}),
)
Quá 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 thì sao?
Nếu các yêu cầu xây dựng của bạn đặc biệt yêu cầu kiểm tra nền tảng, bạn có thể chuyển đổi 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 thực hiện việc này; thao tác này sẽ hạn chế quá mức bản dựng của bạn và gây nhầm lẫn cho người dùng khi điều kiện dự kiến không so khớp.