ویژگی های ساخت قابل تنظیم

ویژگی‌های قابل تنظیم ، که معمولاً با نام select() شناخته می‌شود، یک ویژگی Bazel است که به کاربران اجازه می‌دهد مقادیر ویژگی‌های قانون ساخت را در خط فرمان تغییر دهند.

این را می توان به عنوان مثال برای یک کتابخانه چند پلتفرمی که به طور خودکار پیاده سازی مناسب را برای معماری انتخاب می کند، یا برای یک باینری با قابلیت تنظیم ویژگی که می تواند در زمان ساخت سفارشی شود، استفاده شود.

مثال

# 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",
    },
)

این یک cc_binary را اعلام می کند که دپ های خود را بر اساس پرچم های خط فرمان "انتخاب" می کند. به طور خاص، 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() به عنوان یک مکان نگهدار برای مقداری عمل می کند که بر اساس شرایط پیکربندی انتخاب می شود، که برچسب هایی هستند که به اهداف config_setting ارجاع می دهند. با استفاده از select() در یک ویژگی قابل تنظیم، ویژگی به طور موثر مقادیر متفاوتی را زمانی که شرایط مختلف برقرار است، اتخاذ می کند.

منطبق ها باید بدون ابهام باشند: یا دقیقاً یک شرط باید مطابقت داشته باشد یا اگر چندین شرط مطابقت داشته باشند، values یک فرد باید ابرمجموعه دقیقی از همه شرط های دیگر باشد. به عنوان مثال، values = {"cpu": "x86", "compilation_mode": "dbg"} یک تخصص بدون ابهام از values = {"cpu": "x86"} . شرط داخلی //conditions:default به طور خودکار مطابقت دارد زمانی که هیچ چیز دیگری انجام نمی دهد.

در حالی که این مثال از deps استفاده می کند، select() select روی srcs ، resources ، cmd و بسیاری از ویژگی های دیگر به خوبی کار می کند. فقط تعداد کمی از ویژگی‌ها غیرقابل تنظیم هستند و به وضوح مشروح شده‌اند. برای مثال، ویژگی values خود config_setting غیر قابل تنظیم است.

select() و وابستگی ها

ویژگی های خاص پارامترهای ساخت را برای همه وابستگی های گذرا تحت یک هدف تغییر می دهند. برای مثال، tools genrule --cpu را به CPU دستگاهی که Bazel را اجرا می کند تغییر می دهد (که به لطف کامپایل متقابل ممکن است با CPU مورد نظر متفاوت باشد). این به عنوان یک انتقال پیکربندی شناخته می شود.

داده شده

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

در حال اجرا

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

در یک ماشین توسعه دهنده x86 بیلد را به g_arm.src ، tool1 و x86tool.cc می کند. هر دو select متصل به my_genrule از پارامترهای ساخت my_genrule استفاده می‌کنند که شامل --cpu=arm است. ویژگی tools --cpu را به x86 برای tool1 و وابستگی های انتقالی آن تغییر می دهد. select on tool1 از پارامترهای ساخت tool1 استفاده می کند که شامل --cpu=x86 است.

شرایط پیکربندی

هر کلید در یک ویژگی قابل تنظیم یک مرجع برچسب به config_setting یا constraint_value است.

config_setting فقط مجموعه ای از تنظیمات پرچم خط فرمان مورد انتظار است. با کپسوله کردن آنها در یک هدف، حفظ شرایط "استاندارد" که کاربران می توانند از چندین مکان به آنها مراجعه کنند، آسان است.

constraint_value از رفتار چند پلتفرمی پشتیبانی می کند.

پرچم های داخلی

پرچم هایی مانند --cpu در Bazel تعبیه شده اند: ابزار ساخت به طور بومی آنها را برای همه بیلدها در همه پروژه ها درک می کند. اینها با ویژگی values config_setting مشخص می شوند:

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

flagN یک نام پرچم است (بدون -- ، بنابراین "cpu" به جای "--cpu" ). valueN مقدار مورد انتظار برای آن پرچم است. اگر هر ورودی در values مطابقت داشته باشد :meaningful_condition_name مطابقت دارد. ترتیب بی ربط است

valueN به گونه ای تجزیه می شود که گویی در خط فرمان تنظیم شده است. این یعنی:

  • values = { "compilation_mode": "opt" } مطابق با bazel build -c opt
  • values = { "force_pic": "true" } با bazel build --force_pic=1
  • values = { "force_pic": "0" } با bazel build --noforce_pic

config_setting فقط از پرچم هایی پشتیبانی می کند که بر رفتار هدف تأثیر می گذارد. به عنوان مثال، --show_progress مجاز نیست زیرا تنها بر نحوه گزارش پیشرفت Bazel به کاربر تأثیر می گذارد. اهداف نمی توانند از آن پرچم برای ساختن نتایج خود استفاده کنند. مجموعه دقیق پرچم‌های پشتیبانی شده مستند نشده است. در عمل، بیشتر پرچم هایی که «معنا دارند» کار می کنند.

پرچم های سفارشی

شما می توانید پرچم های خاص پروژه خود را با تنظیمات ساخت Starlark مدل کنید. برخلاف پرچم‌های داخلی، این پرچم‌ها به عنوان اهداف ساخت تعریف می‌شوند، بنابراین Bazel آنها را با برچسب‌های هدف ارجاع می‌دهد.

اینها با ویژگی config_setting flag_values می شوند:

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

رفتار مانند پرچم های داخلی است. برای نمونه کار اینجا را ببینید.

--define یک نحو جایگزین جایگزین برای پرچم های سفارشی است (به عنوان مثال --define foo=bar ). این را می توان در ویژگی مقادیر ( values = {"define": "foo=bar"} ) یا ویژگی define_values ​​( define_values = {"foo": "bar"} کرد. --define فقط برای سازگاری با عقب پشتیبانی می شود. هر زمان که ممکن است تنظیمات ساخت Starlark را ترجیح دهید.

values ، flag_values ، و define_values ​​به طور مستقل ارزیابی می شوند. اگر همه مقادیر در همه آنها مطابقت داشته باشند، config_setting مطابقت دارد.

شرط پیش فرض

شرط داخلی //conditions:default زمانی مطابقت دارد که هیچ شرط دیگری مطابقت نداشته باشد.

به دلیل قانون «دقیقاً یک تطبیق»، یک ویژگی قابل تنظیم بدون تطابق و بدون شرط پیش‌فرض، خطای "no matching conditions" را منتشر می‌کند. این می تواند در برابر خرابی های بی صدا ناشی از تنظیمات غیرمنتظره محافظت کند:

# 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

برای خطاهای حتی واضح‌تر، می‌توانید پیام‌های سفارشی را با ویژگی no_match_error در select() تنظیم کنید.

بستر، زمینه

در حالی که توانایی تعیین چند پرچم در خط فرمان انعطاف‌پذیری را فراهم می‌کند، تنظیم جداگانه هر یک از آنها هر بار که می‌خواهید هدفی را بسازید نیز می‌تواند دشوار باشد. پلتفرم‌ها به شما امکان می‌دهند این موارد را در بسته‌های ساده ادغام کنید.

# 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",
    ],
)

پلتفرم را می توان در خط فرمان مشخص کرد. این config_setting s را فعال می کند که حاوی زیرمجموعه ای از constraint_values ​​پلتفرم است و به آن config_setting اجازه می دهد تا در عبارت select() مطابقت داشته باشند.

به عنوان مثال، برای تنظیم ویژگی srcs my_rocks به calcite.sh ، می توانید به سادگی اجرا کنید.

bazel build //my_app:my_rocks --platforms=//myapp:marble_platform

بدون پلتفرم، ممکن است چیزی شبیه به این باشد

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

select() همچنین می‌تواند مستقیماً constraint_value s را بخواند:

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

این امر نیاز به config_setting های صفحه دیگ را در زمانی که شما فقط باید مقادیر منفرد را بررسی کنید، کاهش می دهد.

پلتفرم ها هنوز در دست توسعه هستند. برای جزئیات به مستندات مراجعه کنید.

ترکیب select() s

select می تواند چندین بار در یک ویژگی ظاهر شود:

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 نمی تواند در select دیگری ظاهر شود. اگر می‌خواهید انتخاب‌ها را در selects قرار دهید و مشخصه شما اهداف دیگری را به عنوان مقادیر می‌گیرد، از یک هدف میانی استفاده کنید:

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

اگر به select برای مطابقت با شرایط متعدد نیاز دارید، AND chaining را در نظر بگیرید.

یا زنجیر زدن

موارد زیر را در نظر بگیرید:

sh_binary(
    name = "my_target",
    srcs = ["always_include.sh"],
    deps = select({
        ":config1": [":standard_lib"],
        ":config2": [":standard_lib"],
        ":config3": [":standard_lib"],
        ":config4": [":special_lib"],
    }),
)

اکثر شرایط به همان عمق ارزیابی می شوند. اما خواندن و حفظ این نحو دشوار است. خوب است که مجبور نباشید [":standard_lib"] چندین بار تکرار کنید.

یک گزینه این است که مقدار را به عنوان یک متغیر 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"],
    }),
)

این امر مدیریت وابستگی را آسان تر می کند. اما همچنان باعث تکرارهای غیر ضروری می شود.

برای پشتیبانی مستقیم تر، از یکی از موارد زیر استفاده کنید:

selects.with_or

ماکرو with_or در ماژول Selects Skylib از شرایط OR مستقیماً در یک selects 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

ماکرو config_setting_group در ماژول config_setting پشتیبانی OR selects :

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

بر خلاف selects.with_or ، اهداف مختلف می توانند :config1_or_2 را در بین ویژگی های مختلف به اشتراک بگذارند.

تطابق چند شرط یک خطا است، مگر اینکه یکی «تخصص» واضح دیگری باشد. برای جزئیات اینجا را ببینید.

و زنجیر

اگر به یک شاخه select برای مطابقت با شرایط متعدد نیاز دارید، از 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"],
    }),
)

برخلاف OR chaining، config_setting های موجود را نمی توان مستقیماً در یک select AND ویرایش کرد. شما باید به صراحت آنها را در یک config_setting_group .

پیام های خطای سفارشی

به طور پیش‌فرض، زمانی که هیچ شرطی مطابقت نداشته باشد، هدفی که select() به آن متصل است با خطای زیر شکست می‌خورد:

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

این را می توان با ویژگی 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

سازگاری قوانین

پیاده سازی قوانین مقادیر حل شده از ویژگی های قابل تنظیم را دریافت می کنند. به عنوان مثال، با توجه به:

# myapp/BUILD

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

کد اجرای قانون ctx.attr.some_attr را به صورت [":foo"] می بیند.

ماکروها می‌توانند بندهای select() را بپذیرند و آن‌ها را به قوانین بومی منتقل کنند. اما آنها نمی توانند مستقیماً آنها را دستکاری کنند . برای مثال، هیچ راهی برای تبدیل ماکرو وجود ندارد

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

به

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

این به دو دلیل است.

اولاً، ماکروهایی که باید بدانند انتخابی کدام مسیر را select می‌کند، نمی‌توانند کار کنند ، زیرا ماکروها در مرحله بارگذاری Bazel ارزیابی می‌شوند، که قبل از مشخص شدن مقادیر پرچم اتفاق می‌افتد. این یک محدودیت اصلی طراحی Bazel است که بعید است به این زودی تغییر کند.

دوم، ماکروهایی که فقط باید روی همه مسیرهای select تکرار شوند، در حالی که از نظر فنی امکان پذیر هستند، فاقد یک رابط کاربری منسجم هستند. طراحی بیشتر برای تغییر این امر ضروری است.

پرس و جو و استعلام بازل

query Bazel در مرحله بارگیری Bazel عمل می کند. این بدان معنی است که نمی داند یک هدف از چه پرچم های خط فرمان استفاده می کند زیرا این پرچم ها تا مراحل بعدی ساخت (در مرحله تجزیه و تحلیل ) ارزیابی نمی شوند. بنابراین نمی تواند تعیین کند که کدام شاخه select() انتخاب شده است.

cquery بعد از مرحله تجزیه و تحلیل Bazel عمل می‌کند، بنابراین تمام این اطلاعات را دارد و می‌تواند به طور دقیق select() s را حل کند.

در نظر گرفتن:

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 بیش از حد تقریبی می کند وابستگی های :my_lib :

$ bazel query 'deps(//myapp:my_lib)'
//myapp:my_lib
//myapp:foo_dep
//myapp:bar_dep

در حالی که cquery وابستگی های دقیق آن را نشان می دهد:

$ bazel cquery 'deps(//myapp:my_lib)' --//myapp:dog_type=pug
//myapp:my_lib
//myapp:bar_dep

سوالات متداول

چرا select() در ماکروها کار نمی کند؟

Select() در قوانین کار می کند! برای جزئیات بیشتر به سازگاری قوانین مراجعه کنید.

مشکل اصلی این سوال معمولاً این است که () select در ماکروها کار نمی کند. اینها با قوانین فرق دارد. برای درک تفاوت، به مستندات قوانین و ماکروها مراجعه کنید. در اینجا یک مثال انتها به انتها آورده شده است:

تعریف قانون و کلان:

# 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)

قاعده و کلان را مثال بزنید:

# 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",
    }),
)

ساختمان با شکست مواجه می شود زیرا sad_macro نمی تواند 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.

وقتی که 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.

تغییر این غیرممکن است زیرا طبق تعریف ماکروها قبل از خواندن پرچم‌های خط فرمان ساخت توسط Bazel ارزیابی می‌شوند. این بدان معناست که اطلاعات کافی برای ارزیابی انتخاب()ها وجود ندارد.

با این حال، ماکروها می توانند s select() را به عنوان حباب های مات به قوانین منتقل کنند:

# 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.

چرا () select همیشه true برمی گرداند؟

از آنجا که ماکروها (اما نه قوانین) بنا به تعریف نمی توانند select() s را ارزیابی کنند، هر تلاشی برای انجام این کار معمولاً یک خطا ایجاد می کند:

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().

Booleans یک مورد خاص است که بی‌صدا با شکست مواجه می‌شود، بنابراین باید مراقب آن‌ها باشید:

$ 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.

این به این دلیل اتفاق می افتد که ماکروها محتوای select() را درک نمی کنند. بنابراین چیزی که آنها واقعاً ارزیابی می کنند، خود شی select() است. طبق استانداردهای طراحی پایتونیک ، تمام اشیاء به غیر از تعداد بسیار کمی از استثناها به طور خودکار true را برمی‌گردانند.

آیا می توانم select() را مانند دیکت بخوانم؟

ماکروها نمی‌توانند انتخاب(ها) را ارزیابی کنند، زیرا ماکروها قبل از اینکه Bazel بداند پارامترهای خط فرمان build چیست، ارزیابی می‌شوند. آیا حداقل می توانند فرهنگ لغت select() را بخوانند تا مثلاً یک پسوند به هر مقدار اضافه کنند؟

از نظر مفهومی این امکان پذیر است، اما هنوز یکی از ویژگی های Bazel نیست. کاری که امروز می توانید انجام دهید این است که یک دیکشنری مستقیم تهیه کنید، سپس آن را به یک 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

اگر می خواهید از هر دو نوع select() و native پشتیبانی کنید، می توانید این کار را انجام دهید:

$ 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 + "> $@",
    )

چرا select() با bind() کار نمی کند؟

زیرا bind() یک قانون WORKSPACE است نه یک قانون BUILD.

قوانین فضای کاری پیکربندی خاصی ندارند و مانند قوانین BUILD ارزیابی نمی شوند. بنابراین، یک select() در bind() در واقع نمی تواند برای هیچ شاخه خاصی ارزیابی شود.

در عوض، برای انجام این نوع تعیین زمان اجرا، باید از alias() با یک select() در ویژگی actual استفاده کنید. این به درستی کار می کند، زیرا alias() یک قانون BUILD است و با یک پیکربندی خاص ارزیابی می شود.

در صورت نیاز، حتی می توانید یک نقطه هدف bind() به یک alias() داشته باشید.

$ 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",
    }),
)

با این تنظیمات، می‌توانید --define ssl_library=alternative را پاس کنید، و هر هدفی که به //:ssl یا //external:ssl بستگی دارد، جایگزین واقع در @alternative//:ssl را می‌بیند.

چرا select() من چیزی را که انتظار دارم انتخاب نمی کند؟

اگر //myapp:foo یک select() دارد که شرایط مورد انتظار شما را انتخاب نمی کند، از cquery و bazel config bazel برای اشکال زدایی استفاده کنید:

اگر //myapp:foo هدف سطح بالایی است که می‌سازید، اجرا کنید:

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

اگر هدف //bar دیگری را می سازید که به //myapp:foo در جایی در زیرگراف آن بستگی دارد، اجرا کنید:

$ bazel cquery 'somepath(//bar, //myapp:foo)' <desired build flags>
//bar:bar   (3ag3193fee94a2)
//bar:intermediate_dep (12e23b9a2b534a)
//myapp:foo (12e23b9a2b534a)

(12e23b9a2b534a) در کنار //myapp:foo یک هش از پیکربندی است که //myapp:foo 's select() حل می کند. می توانید مقادیر آن را با 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]
  ...
}
...

سپس این خروجی را با تنظیمات مورد انتظار هر config_setting کنید.

//myapp:foo ممکن است در پیکربندی های مختلف در یک بیلد وجود داشته باشد. برای راهنمایی در مورد استفاده از somepath برای دریافت مسیر مناسب، به اسناد cquery مراجعه کنید.

چرا select() با پلتفرم ها کار نمی کند؟

Bazel از ویژگی‌های قابل تنظیم برای بررسی اینکه آیا یک پلتفرم معین پلتفرم هدف است یا خیر پشتیبانی نمی‌کند زیرا معنایی آن نامشخص است.

مثلا:

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": [],
    }),
)

در این فایل BUILD ، اگر پلتفرم هدف دارای محدودیت‌های @platforms//cpu:x86 و @platforms//os:linux باشد، باید از کدام select() استفاده شود، اما آیا :x86_linux_platform در اینجا تعریف نشده است؟ نویسنده فایل BUILD و کاربری که پلتفرم جداگانه را تعریف کرده است ممکن است ایده های متفاوتی داشته باشند.

به جای آن چه باید بکنم؟

در عوض، یک config_setting تعریف کنید که با هر پلتفرمی با این محدودیت ها مطابقت داشته باشد:

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": [],
    }),
)

این فرآیند معناشناسی خاصی را تعریف می‌کند و برای کاربران روشن‌تر می‌کند که چه پلتفرم‌هایی شرایط مورد نظر را برآورده می‌کنند.

اگر واقعاً بخواهم در پلتفرم select کنم، چه؟

اگر الزامات ساخت شما به طور خاص نیاز به بررسی پلت فرم دارد، می توانید مقدار پرچم --platforms را در یک 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": [],
    }),
)

تیم بازل انجام این کار را تایید نمی کند. بیش از حد ساخت شما را محدود می کند و در زمانی که شرایط مورد انتظار مطابقت ندارد، کاربران را گیج می کند.