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 bản dựng tại dòng lệnh.
Ví dụ: bạn có thể sử dụng tính năng này cho một thư viện đa nền tảng tự động chọn cách triển khai phù hợp cho cấu trúc hoặc cho một tệp nhị phân có thể định cấu hình tính năng có thể được tuỳ chỉnh tại thời điểm tạo bản dựng.
Ví dụ:
# myapp/BUILD
cc_binary(
name = "mybinary",
srcs = ["main.cc"],
deps = select({
":arm_build": [":arm_lib"],
":x86_debug_build": [":x86_dev_lib"],
"//conditions:default": [":generic_lib"],
}),
)
config_setting(
name = "arm_build",
values = {"cpu": "arm"},
)
config_setting(
name = "x86_debug_build",
values = {
"cpu": "x86",
"compilation_mode": "dbg",
},
)
Thao tác này khai báo một cc_binary
"chọn" các phần phụ thuộc dựa trên cờ tại dòng lệnh. Cụ thể, deps
trở thành:
Lệnh | 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à các nhãn tham chiếu đến mục tiêu config_setting
. Bằng cách sử dụng select()
trong một thuộc tính có thể định cấu hình, thuộc tính này sẽ áp dụng hiệu quả các giá trị khác nhau khi các điều kiện khác nhau được đáp ứng.
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ì:
* 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, đây là {"@platforms//os:linux": "Hello", "@platforms//cpu:x86_64": "Hello"}
rõ ràng vì cả hai nhánh đều phân giải thành "hello".
* values
của một người là tập hợp con nghiêm ngặt của tất cả các values
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 khớp.
Mặc dù ví dụ này sử dụng deps
, nhưng 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 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 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ụ: tools
của genrule
thay đổi --cpu
thành CPU của máy chạy Bazel (nhờ tính năng biên dịch chéo, có thể khác với CPU mà mục tiêu được tạo). Đây được gọi là 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áy của nhà phát triển x86
liên kết bản dựng với g_arm.src
, tool1
và
x86tool.cc
. Cả hai select
đính kèm vào my_genrule
đều sử dụng các tham số bản dựng của my_genrule
, bao gồm cả --cpu=arm
. Thuộc tính tools
thay đổi --cpu
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 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 sẵn vào Bazel: công cụ bản dựng hiểu được các cờ này cho tất cả 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 values
của config_setting
:
config_setting(
name = "meaningful_condition_name",
values = {
"flag1": "value1",
"flag2": "value2",
...
},
)
flagN
là tên cờ (không có --
, vì vậy là "cpu"
thay vì "--cpu"
). valueN
là giá trị dự kiến cho cờ đó. :meaningful_condition_name
khớp nếu mọi mục nhập trong values
khớp. Thứ tự không liên quan.
valueN
được phân tích cú pháp như thể được đặt trên dòng lệnh. Điều này có nghĩa là:
values = { "compilation_mode": "opt" }
đối sánhbazel build -c opt
values = { "force_pic": "true" }
đối sánhbazel build --force_pic=1
values = { "force_pic": "0" }
đối sánhbazel 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ì 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 ghi lại chính xác tập hợp 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ể 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, các cờ này được xác định là mục tiêu bản dựng, vì vậy Bazel tham chiếu các cờ này 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 tương tự như đối với cờ tích hợp. Hãy xem tại đây để biết ví dụ về cách hoạt động.
--define
là cú pháp cũ thay thế cho cờ tuỳ chỉnh (ví dụ: --define foo=bar
). Bạn có thể thể hiện 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 sử dụng 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
khớp nếu tất cả giá trị trên tất cả các giá trị đó 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 "chính xác một lần khớp", một thuộc tính có thể định cấu hình không khớp và không có điều kiện mặc định sẽ phát ra lỗi "no matching conditions"
. Điều này có thể giúp bảo vệ khỏi các lỗi thầm lặng do các chế độ cài đặt không mong muốn:
# myapp/BUILD
config_setting(
name = "x86_cpu",
values = {"cpu": "x86"},
)
cc_library(
name = "x86_only_lib",
srcs = select({
":x86_cpu": ["lib.cc"],
}),
)
$ bazel build //myapp:x86_only_lib --cpu=arm
ERROR: Configurable attribute "srcs" doesn't match this configuration (would
a default condition help?).
Conditions checked:
//myapp:x86_cpu
Để biết 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 tính linh hoạt, nhưng việc thiết lập từng cờ riêng lẻ 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 các gói 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. Phương thức này 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 trong biểu thức select()
.
Ví dụ: để đặt thuộc tính srcs
của my_rocks
thành calcite.sh
, bạn chỉ cần chạy
bazel build //my_app:my_rocks --platforms=//myapp:marble_platform
Nếu không có nền tảng, mã này có thể có dạng như sau
bazel build //my_app:my_rocks --define color=white --define texture=smooth --define type=metamorphic
select()
cũng có thể trực tiếp đọc constraint_value
:
constraint_setting(name = "type")
constraint_value(name = "igneous", constraint_setting = "type")
constraint_value(name = "metamorphic", constraint_setting = "type")
sh_binary(
name = "my_rocks",
srcs = select({
":igneous": ["igneous.sh"],
":metamorphic" ["metamorphic.sh"],
}),
)
Điều này giúp bạn không cần đến config_setting
nguyên mẫu khi chỉ cần kiểm tra các giá trị đơn lẻ.
Các nền tảng vẫn đang trong quá trình phát triển. Hãy xem tài liệu để biết thông tin chi tiết.
Kết hợp select()
select
có thể xuất hiện nhiều lần trong cùng một thuộc tính:
sh_binary(
name = "my_target",
srcs = ["always_include.sh"] +
select({
":armeabi_mode": ["armeabi_src.sh"],
":x86_mode": ["x86_src.sh"],
}) +
select({
":opt_mode": ["opt_extras.sh"],
":dbg_mode": ["dbg_extras.sh"],
}),
)
select
không được xuất hiện bên trong một select
khác. Nếu bạn cần lồng selects
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
khớp khi nhiều điều kiện khớp, hãy cân nhắc AND
chuỗi.
Xâu chuỗi OR
Hãy cân nhắc thực hiện những bước sau:
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": [":standard_lib"],
":config2": [":standard_lib"],
":config3": [":standard_lib"],
":config4": [":special_lib"],
}),
)
Hầu hết các điều kiện đều đánh giá đến cùng một phần phụ thuộc. Nhưng cú pháp này khó đọc và duy trì. Thật tuyệt nếu bạn không phải lặp lại [":standard_lib"]
nhiều lần.
Một lựa chọn là xác định trước giá trị dưới dạng biến BUILD:
STANDARD_DEP = [":standard_lib"]
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1": STANDARD_DEP,
":config2": STANDARD_DEP,
":config3": STANDARD_DEP,
":config4": [":special_lib"],
}),
)
Điều này giúp bạn dễ dàng quản lý phần phụ thuộc. Tuy nhiên, 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
ing ngay bên trong select
:
load("@bazel_skylib//lib:selects.bzl", "selects")
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = selects.with_or({
(":config1", ":config2", ":config3"): [":standard_lib"],
":config4": [":special_lib"],
}),
)
selects.config_setting_group
Macro config_setting_group trong mô-đun selects
của Skylib hỗ trợ OR
nhiều config_setting
:
load("@bazel_skylib//lib:selects.bzl", "selects")
config_setting(
name = "config1",
values = {"cpu": "arm"},
)
config_setting(
name = "config2",
values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
name = "config1_or_2",
match_any = [":config1", ":config2"],
)
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1_or_2": [":standard_lib"],
"//conditions:default": [":other_lib"],
}),
)
Không giống như selects.with_or
, các mục tiêu khác nhau có thể chia sẻ :config1_or_2
trên nhiều thuộc tính.
Lỗi nhiều điều kiện khớp với nhau, trừ phi một điều kiện là "chuyên biệt" rõ ràng của các điều kiện khác hoặc tất cả đều phân giải thành cùng một giá trị. 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 khớp, hãy sử dụng macro Skylib config_setting_group:
config_setting(
name = "config1",
values = {"cpu": "arm"},
)
config_setting(
name = "config2",
values = {"compilation_mode": "dbg"},
)
selects.config_setting_group(
name = "config1_and_2",
match_all = [":config1", ":config2"],
)
sh_binary(
name = "my_target",
srcs = ["always_include.sh"],
deps = select({
":config1_and_2": [":standard_lib"],
"//conditions:default": [":other_lib"],
}),
)
Không giống như việc nối OR, bạn không thể trực tiếp AND
các config_setting
hiện có bên trong select
. Bạn phải gói các thành phần này 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 khớp, mục tiêu mà select()
được đính kèm sẽ không thành công với lỗi:
ERROR: Configurable attribute "deps" doesn't match this configuration (would
a default condition help?).
Conditions checked:
//tools/cc_target_os:darwin
//tools/cc_target_os:android
Bạn có thể tuỳ chỉnh thuộc tính này bằng thuộc tính no_match_error
:
cc_library(
name = "my_lib",
deps = select(
{
"//tools/cc_target_os:android": [":android_deps"],
"//tools/cc_target_os:windows": [":windows_deps"],
},
no_match_error = "Please build with an Android or Windows toolchain",
),
)
$ bazel build //myapp:my_lib
ERROR: Configurable attribute "deps" doesn't match this configuration: Please
build with an Android or Windows toolchain
Khả năng tương thích của quy tắc
Các hoạt động triển khai quy tắc sẽ nhận được giá trị đã phân giải của các thuộc tính có thể định cấu hình. Ví dụ: giả sử:
# myapp/BUILD
some_rule(
name = "my_target",
some_attr = select({
":foo_mode": [":foo"],
":bar_mode": [":bar"],
}),
)
$ bazel build //myapp/my_target --define mode=foo
Mã triển khai quy tắc xem ctx.attr.some_attr
là [":foo"]
.
Macro có thể chấp nhận mệnh đề select()
và truyền các mệnh đề đó đến các quy tắc gốc. Tuy nhiên, các lớp này không thể trực tiếp thao tác với các lớp đó. Ví dụ: không có cách nào để một macro chuyển đổi
select({"foo": "val"}, ...)
tới
select({"foo": "val_with_suffix"}, ...)
Có hai lý do cho việc này.
Trước tiên, các macro cần biết select
sẽ chọn đường dẫn nào không thể hoạt động
vì các macro được đánh giá trong giai đoạn tải của Bazel,
việc này xảy ra trước khi biết được giá trị cờ.
Đây là một quy tắc hạn chế thiết kế cốt lõi của Bazel và có thể sẽ không thay đổi trong thời gian sắp tới.
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ả 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 điều này.
Truy vấn Bazel và cquery
Bazel query
hoạt động trong giai đoạn tải của Bazel.
Điều này có nghĩa là công cụ này không biết mục tiêu sử dụng cờ dòng lệnh nào vì các cờ đó sẽ không được đánh giá cho đến sau này trong bản dựng (trong giai đoạn phân tích).
Do đó, trình biên dịch 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, công cụ này có tất cả thông tin này và có thể phân giải chính xác select()
.
Hãy cân nhắc:
load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
# myapp/BUILD
string_flag(
name = "dog_type",
build_setting_default = "cat"
)
cc_library(
name = "my_lib",
deps = select({
":long": [":foo_dep"],
":short": [":bar_dep"],
}),
)
config_setting(
name = "long",
flag_values = {":dog_type": "dachshund"},
)
config_setting(
name = "short",
flag_values = {":dog_type": "pug"},
)
query
ước tính quá nhiều 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() có hoạt động trong các 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.
Câu hỏi này thường có nghĩa là vấn đề chính là select() không hoạt động trong macro. Các điều kiện này khác với quy tắc. Hãy xem tài liệu về quy tắc và macro để hiểu sự khác biệt. Dưới đây là ví dụ toàn diện:
Xác định quy tắc và macro:
# myapp/defs.bzl
# Rule implementation: when an attribute is read, all select()s have already
# been resolved. So it looks like a plain old attribute just like any other.
def _impl(ctx):
name = ctx.attr.name
allcaps = ctx.attr.my_config_string.upper() # This works fine on all values.
print("My name is " + name + " with custom message: " + allcaps)
# Rule declaration:
my_custom_bazel_rule = rule(
implementation = _impl,
attrs = {"my_config_string": attr.string()},
)
# Macro declaration:
def my_custom_bazel_macro(name, my_config_string):
allcaps = my_config_string.upper() # This line won't work with select(s).
print("My name is " + name + " with custom message: " + allcaps)
Tạo bản sao cho quy tắc và macro:
# myapp/BUILD
load("//myapp:defs.bzl", "my_custom_bazel_rule")
load("//myapp:defs.bzl", "my_custom_bazel_macro")
my_custom_bazel_rule(
name = "happy_rule",
my_config_string = select({
"//tools/target_cpu:x86": "first string",
"//third_party/bazel_platforms/cpu:ppc": "second string",
}),
)
my_custom_bazel_macro(
name = "happy_macro",
my_config_string = "fixed string",
)
my_custom_bazel_macro(
name = "sad_macro",
my_config_string = select({
"//tools/target_cpu:x86": "first string",
"//third_party/bazel_platforms/cpu:ppc": "other string",
}),
)
Không tạo được bản dựng vì sad_macro
không thể xử lý select()
:
$ bazel build //myapp:all
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
(most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
ERROR: error loading package 'myapp': Package 'myapp' contains errors.
Quá trình tạo sẽ thành công khi bạn chú thích sad_macro
:
# Comment out sad_macro so it doesn't mess up the build.
$ bazel build //myapp:all
DEBUG: /myworkspace/myapp/defs.bzl:5:3: My name is happy_macro with custom message: FIXED STRING.
DEBUG: /myworkspace/myapp/hi.bzl:15:3: My name is happy_rule with custom message: FIRST STRING.
Bạn không thể thay đổi đ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á các select().
Tuy nhiên, macro có thể truyền select()
dưới dạng blob mờ cho 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?
Theo định nghĩa, macro (nhưng không phải quy tắc) không thể đánh giá select()
, nên mọi nỗ lực đánh giá select()
thường sẽ dẫn đến lỗi:
ERROR: /myworkspace/myapp/BUILD:17:1: Traceback
(most recent call last):
File "/myworkspace/myapp/BUILD", line 17
my_custom_bazel_macro(name = "sad_macro", my_config_stri..."}))
File "/myworkspace/myapp/defs.bzl", line 4, in
my_custom_bazel_macro
my_config_string.upper()
type 'select' has no method upper().
Boolean là một trường hợp đặc biệt không báo lỗi, vì vậy, bạn nên đặc biệt cảnh giác 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({
"//tools/target_cpu:x86": True,
"//third_party/bazel_platforms/cpu:ppc": False,
}),
)
$ bazel build //myapp:all --cpu=x86
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
$ bazel build //mypro:all --cpu=ppc
DEBUG: /myworkspace/myapp/defs.bzl:4:3: TRUE.
Điều này xảy ra vì macro không hiểu được nội dung của select()
.
Vì vậy, những gì chúng thực sự đánh giá là chính đối tượng select()
. Theo tiêu chuẩn thiết kế Pythonic, tất cả các đối tượng ngoại trừ một số ít ngoại lệ sẽ tự động trả về 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 các tham số dòng lệnh của bản dựng. Ít nhất, chúng có thể đọc từ điển của select()
để thêm hậu tố vào mỗi giá trị không?
Về mặt lý thuyết, điều này là có thể, nhưng chưa phải là tính năng của Bazel.
Việc bạn có thể làm hôm nay là chuẩn bị một từ điển đơn thuầ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 tiên, đừng sử dụng bind()
. Phương thức này không còn được dùng nữa mà thay bằng alias()
.
Câu trả lời kỹ thuật là bind()
là một quy tắc repo, chứ không phải quy tắc BUILD.
Quy tắc Repo không có cấu hình cụ thể và không được đánh giá giống như 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. Cách 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 mục tiêu bind()
trỏ đến alias()
nếu cần.
$ cat WORKSPACE
workspace(name = "myapp")
bind(name = "openssl", actual = "//:ssl")
http_archive(name = "alternative", ...)
http_archive(name = "boringssl", ...)
$ cat BUILD
config_setting(
name = "alt_ssl",
define_values = {
"ssl_library": "alternative",
},
)
alias(
name = "ssl",
actual = select({
"//:alt_ssl": "@alternative//:ssl",
"//conditions:default": "@boringssl//:ssl",
}),
)
Với chế độ thiết lập này, bạn có thể truyền --define ssl_library=alternative
và mọi mục tiêu phụ thuộc vào //:ssl
hoặc //external:ssl
sẽ thấy mục tiêu thay thế nằm ở @alternative//:ssl
.
Nhưng thực sự, hãy ngừng sử dụng bind()
.
Tại sao select() không chọn nội dung mà 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 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ố 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 giúp 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 mà mỗi config_setting
dự kiến.
//myapp:foo
có thể tồn tại trong nhiều cấu hình trong cùng một bản dựng. Hãy xem tài liệu về cquery để biết hướng dẫn về cách sử dụng somepath
nhằm chọn đúng hàm.
Tại sao select()
không hoạt động với các nền tảng?
Bazel không hỗ trợ các thuộc tính có thể định cấu hình để kiểm tra xem một nền tảng 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ả điều kiện 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 ở đâ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 đó?
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 yêu cầu bản dựng của bạn yêu cầu cụ thể việc kiểm tra nền tảng, bạn có thể lật giá trị của cờ --platforms
trong config_setting
:
config_setting(
name = "is_specific_x86_linux_platform",
values = {
"platforms": ["//package:x86_linux_platform"],
},
)
cc_library(
name = "lib",
srcs = [...],
linkopts = select({
":is_specific_x86_linux_platform": ["--enable_x86_optimizations"],
"//conditions:default": [],
}),
)
Nhóm Bazel không ủng hộ việc này; việc này sẽ hạn chế quá mức bản dựng và gây nhầm lẫn cho người dùng khi điều kiện dự kiến không khớp.