سمات الإصدار القابلة للإعداد

السمات القابلة للضبط، المعروفة أيضًا باسم 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 عن "Choose;" الانخفاضات استنادًا إلى العلامات في سطر الأوامر. على وجه التحديد، يصبح deps:

Command 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() أيضًا على srcs وresources وcmd ومعظم السمات الأخرى. هناك عدد قليل فقط من السمات تكون غير قابلة للإعداد، ويتم وضع تعليقات توضيحية عليها بوضوح. على سبيل المثال، لا يمكن ضبط السمة config_setting'svalues.

select() والاعتماديات

تغيّر بعض السمات معلمات الإصدار لجميع التبعيات الانتقالية ضمن هدف. على سبيل المثال، genruletools تغيّر --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 على tool1 معلمات الإصدار tool1's التي تتضمن --cpu=x86.

شروط الإعداد

يشير كل مفتاح في سمة قابلة للضبط إلى مرجع تصنيف إلى config_setting أو constraint_value.

config_setting هي مجموعة من إعدادات علامات سطر الأوامر المتوقعة. ومن خلال تضمين هذه المصطلحات في هدف، سيكون من السهل الحفاظ على &الشروط والتعبير العادي&quot؛ بحيث يمكن للمستخدمين الإشارة إليها من أماكن متعددة.

يقدم constraint_value توافقًا مع السلوك من عدّة منصات.

علامات المضمّنة

تُدمج العلامات، مثل --cpu، في Bazel، وهي أداة تُعنى بفهم جميع الإصدارات في جميع المشاريع. ويتم تحديدها باستخدام السمة config_setting's values:

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

flagN هو اسم علم (بدون --، لذلك يكون "cpu" بدلاً من "--cpu"). valueN القيمة المتوقّعة لتلك العلامة. يتطابق :meaningful_condition_name إذا تطابق كل إدخال في values. الطلب غير ذي صلة.

يتم تحليل 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'sflag_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

ولتوفير أخطاء أكثر وضوحًا، يمكنك ضبط رسائل مخصَّصة باستخدام السمة select()'sno_match_error.

الأنظمة الأساسية

على الرغم من أنّ إمكانية تحديد علامات متعددة في سطر الأوامر توفّر المرونة، قد يكون من الصعب أيضًا وضع كل علامة بشكل فردي في كل مرة تريد إنشاء هدف. تتيح لك الأنظمة الأساسية دمج هذه المجموعات في حِزم بسيطة.

# 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 التي تحتوي على مجموعة فرعية من 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 مباشرةً:

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

يمكن أن يظهر 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 للمطابقة عندما تتطابق عدة شروط، ننصحك و التسلسل.

سلسلة أو

ننصحك باتّباع الخطوات التالية:

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 في Skylib's selects استخدام ORing الشروط مباشرةً في 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 في Skylib's selects استخدام ORconfig_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"],
    }),
)

وعلى عكس selects.with_or، يمكن أن تستهدِف الأهداف المختلفة :config1_or_2 في سمات مختلفة.

خطأ في مطابقة العديد من الشروط ما لم يكن أحدها غير واضح وخاص بالشروط الأخرى. انظر هنا للحصول على تفاصيل.

AND سلسلة

إذا كنت بحاجة إلى فرع select لمطابقة عدة شروط، استخدِم ماكرو وحدة ماكرو 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"، لا يمكن ANDconfig_setting مباشرةً داخل select. يجب تضمينها بوضوح في 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&#39، والتي تحدث قبل أن تكون قيم العلامات معروفة. ويُعد ذلك أحد القيود الأساسية لتصميم البازلاء والذي من غير المحتمل أن يتغيّر في أي وقت قريب.

ثانيًا، تفتقر وحدات الماكرو التي تحتاج إلى تكرار عبر جميع مسارات select، على الرغم من أنها مجدية من الناحية التقنية، إلى واجهة مستخدم مترابطة. المزيد من التصميم ضروري لتغيير هذا.

طلب بحث Bazel وcquery

يعمل البازيل query فوق مرحلة التحميل في Bazel's. وهذا يعني أنها لا تتعرّف على سطر الأوامر الذي تضعه الأهداف المستهدفة نظرًا لعدم تقييم هذه العلامات حتى وقت لاحق في الإصدار (في مرحلة التحليل). لذلك، لا يمكن تحديد فروع select() التي تم اختيارها.

تعمل البازيل cquery بعد مرحلة التحليل في Bazel&#39، بحيث تحتوي على كل هذه المعلومات يمكنها حل 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

الأسئلة الشائعة

لماذا لا يعمل't 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 علامات سطر الأوامر Build’#39;s. وهذا يعني أنه لا تتوفر معلومات كافية لتقييم select(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() القيمة "صحيح" دائمًا؟

نظرًا لأن وحدات الماكرو (وليس القواعد) بتعريفها يمكن'لتقييم select()، فإن أي محاولة لتنفيذ ذلك عادةً ما تظهر خطأ:

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

المنطق المنطقية هي حالة خاصة تعذّر تنفيذ الإجراء بها تلقائيًا، لذا يجب توخي الحذر الشديد بشأنها:

$ 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() نفسه. وفقًا لمعايير التصميم Pythonic، يتم عرض جميع العناصر باستثناء عدد قليل جدًا من الاستثناءات.

هل يمكنني قراءة select() مثل إملاء؟

يمكن لوحدات الماكرو الماكرو تقييم الاختيارات لأنّ وحدات الماكرو تُقيِّم قبل أن تعرف البازل معلَمات سطر الأوامر Build'#. هل يمكنهم على الأقل قراءة قاموس select()'s، على سبيل المثال، لإضافة لاحقة إلى كل قيمة؟

من الناحية النظرية، يمكن تحقيق ذلك، ولكنها ليست ميزة 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() وأنواع الإعلانات المدمجة مع المحتوى، يمكنك إجراء ما يلي:

$ 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 select() مع bind()؟

لأن bind() هي قاعدة WORKSPACE، وليست قاعدة BUILD.

لا تتضمّن قواعد Workspace إعدادات محدَّدة، ولا يتم تقييمها بالطريقة نفسها التي يتم بها تقييم قواعد 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.

لماذا لا يختار't Select() ما أتوقعه؟

إذا كان لدى //myapp:foo select() لا يختار الشرط الذي تتوقعه، يمكنك استخدام cquery وbazel config لتصحيح الأخطاء:

إذا كان //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 بتنسيقات مختلفة في الإصدار نفسه. راجِع مستندات cquery للحصول على إرشادات حول استخدام somepath للحصول على المستند الصحيح.

لماذا لا يعمل 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 هذا، أي select() يجب استخدامه إذا كان النظام الأساسي المستهدَف يشتمل على كلٍّ من القيود @platforms//cpu:x86 و@platforms//os:linux، ولم يتم تعريف :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": [],
    }),
)

لا يصادق فريق Bazel على هذا الأمر، لأنه يقيد مشروعك بشكل زائد ويخلط بين المستخدمين عندما لا يتطابق الشرط المتوقع.