تصف هذه الصفحة إطار عمل سلسلة الأدوات، الذي يُعتبر طريقة لمؤلفي القواعد لفصل منطق القواعد من اختيار الأدوات المستندة إلى النظام الأساسي. وننصحك بقراءة صفحات القواعد والمنصات قبل المتابعة. تتناول هذه الصفحة سبب أهمية سلاسل الأدوات، وكيفية تحديدها واستخدامها، وكيفية اختيار Bazel لسلسلة أدوات مناسبة استنادًا إلى قيود النظام الأساسي.
الحافز
لنتعرّف أولاً على سلاسل الأدوات التي تهدف إلى حلّ المشاكل. لنفترض أنك تكتب قواعد لدعم لغة البرمجة "bar". ستعمل القاعدة bar_binary
على تجميع *.bar
ملف باستخدام المُجمِّع barc
، وهي أداة تم إنشاؤها كهدف آخر في مساحة العمل. ونظرًا لأن المستخدمين الذين يكتبون استهدافات bar_binary
لا ينبغي أن يحددوا تبعية على برنامج التجميع، يمكنك جعله تبعية ضمنية عن طريق إضافتها إلى تعريف القاعدة كسمة خاصة.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
"_compiler": attr.label(
default = "//bar_tools:barc_linux", # the compiler running on linux
providers = [BarcInfo],
),
},
)
تعتمد //bar_tools:barc_linux
الآن على كل هدف bar_binary
، لذا سيتم إنشاؤه قبل أي هدف bar_binary
. ويمكن الوصول إليها من خلال وظيفة تنفيذ القاعدة مثل أي سمة أخرى:
BarcInfo = provider(
doc = "Information about how to invoke the barc compiler.",
# In the real world, compiler_path and system_lib might hold File objects,
# but for simplicity they are strings for this example. arch_flags is a list
# of strings.
fields = ["compiler_path", "system_lib", "arch_flags"],
)
def _bar_binary_impl(ctx):
...
info = ctx.attr._compiler[BarcInfo]
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
وتكمن المشكلة هنا في أنه تم تصنيف تصنيف برنامج التجميع إلى bar_binary
، ومع ذلك قد تحتاج الاستهدافات المختلفة إلى برامج تصنيف مختلفة اعتمادًا على النظام الأساسي الذي يتم إنشاؤها عليه والنظام الأساسي الذي يتم إنشاؤه عليها -- وتسمى
النظام الأساسي المستهدف والنظام الأساسي للتنفيذ، على التوالي. بالإضافة إلى ذلك، لا يعرف مؤلف القاعدة بالضرورة جميع الأدوات والأنظمة الأساسية المتاحة، لذا
لا يمكن ترميزها في تعريف القاعدة.
يتمثل الحلّ الأقل فعالية في نقل الأعباء إلى المستخدمين من خلال جعل السمة _compiler
غير خاصة. وبعد ذلك، يمكن ترميز الأهداف الفردية لإنشاء نظام أساسي أو آخر.
bar_binary(
name = "myprog_on_linux",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_linux",
)
bar_binary(
name = "myprog_on_windows",
srcs = ["mysrc.bar"],
compiler = "//bar_tools:barc_windows",
)
يمكنك تحسين هذا الحل باستخدام select
لاختيار compiler
استنادًا إلى النظام الأساسي:
config_setting(
name = "on_linux",
constraint_values = [
"@platforms//os:linux",
],
)
config_setting(
name = "on_windows",
constraint_values = [
"@platforms//os:windows",
],
)
bar_binary(
name = "myprog",
srcs = ["mysrc.bar"],
compiler = select({
":on_linux": "//bar_tools:barc_linux",
":on_windows": "//bar_tools:barc_windows",
}),
)
الأمر الذي قد يكون مملًا ويتعلّق كثيرًا من كل مستخدم bar_binary
.
وإذا لم يتم استخدام هذا النمط بشكل متسق على مستوى مساحة العمل، يؤدي ذلك إلى إنشاء إصدارات تعمل بشكل جيد على نظام أساسي واحد، ولكنها تفشل عند تمديدها لتشمل سيناريوهات متعددة الأنظمة الأساسية. ولا تعالج أيضًا مشكلة إضافة الدعم للأنظمة الأساسية وعارضي البرامج الجدد بدون تعديل القواعد أو الأهداف الحالية.
يحل إطار عمل هذه الأداة هذه المشكلة من خلال إضافة مستوى إضافي من التوجيه. بشكل أساسي، أنت تُعلن أنّ قاعدتك تستند إلى اعتمادية مجرّدة على بعض أفراد مجموعة الأهداف (نوع سلسلة الأدوات)، وتحدِّد Bazel هذا الإجراء تلقائيًا لاستهداف معيّن (سلسلة أدوات). ) بناءً على قيود النظام الأساسي الساري. ولا يحتاج مؤلف القاعدة أو المؤلف المستهدف إلى معرفة المجموعة الكاملة من المنصات وسلاسل الأدوات المتاحة.
قواعد الكتابة التي تستخدم سلاسل الأدوات
ضمن إطار عمل سلسلة الأدوات، بدلاً من الاعتماد على القواعد مباشرةً، يعتمد الأمر بدلاً من ذلك على أنواع سلاسل الأدوات. ويُعدّ نوع سلسلة الأدوات هدفًا بسيطًا يمثّل فئة من الأدوات التي تؤدي الدور نفسه للمنصات المختلفة. على سبيل المثال، يمكنك تحديد نوع يمثّل برنامج التجميع في الشريط:
# By convention, toolchain_type targets are named "toolchain_type" and
# distinguished by their package path. So the full path for this would be
# //bar_tools:toolchain_type.
toolchain_type(name = "toolchain_type")
تم تعديل تعريف القاعدة في القسم السابق بحيث يشير إلى أنّه يستخدم سلسلة أدوات //bar_tools:toolchain_type
بدلاً من تضمين المُجمِّع كسمة.
bar_binary = rule(
implementation = _bar_binary_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
...
# No `_compiler` attribute anymore.
},
toolchains = ["//bar_tools:toolchain_type"],
)
تصل وظيفة التنفيذ الآن إلى هذه التبعية ضمن ctx.toolchains
بدلاً من ctx.attr
، باستخدام نوع سلسلة الأدوات كمفتاح.
def _bar_binary_impl(ctx):
...
info = ctx.toolchains["//bar_tools:toolchain_type"].barcinfo
# The rest is unchanged.
command = "%s -l %s %s" % (
info.compiler_path,
info.system_lib,
" ".join(info.arch_flags),
)
...
تعرض ctx.toolchains["//bar_tools:toolchain_type"]
موفّر
ToolchainInfo
لأي هدف من Bazel تمثّل اعتمادية سلسلة الأدوات عليه. يتم ضبط حقول العنصر ToolchainInfo
من خلال قاعدة الأداة الأساسية. في القسم التالي، يتم تعريف هذه القاعدة بحيث يكون هناك حقل barcinfo
يلتف حول عنصر BarcInfo
.
يتم وصف إجراء البازل لحل سلاسل الأدوات للأهداف أدناه. الهدف الوحيد لسلسلة الأدوات الذي تم حلّه هو الاعتمادية على هدف bar_binary
، وليس المساحة الكاملة لسلاسل أدوات المرشح.
سلاسل الأدوات الإلزامية والاختيارية
بشكل تلقائي، عندما تعبِّر قاعدة عن تبعية لنوع سلسلة الأدوات باستخدام تصنيف مجرّد (كما هو موضّح أعلاه)، يُعتبر نوع سلسلة الأدوات إلزاميًا. إذا تعذَّر على Bazel العثور على سلسلة أدوات مطابقة (يُرجى الاطِّلاع على درجة دقة الأداة أدناه لنوع إلزامي لسلسلة الأدوات، يُرجى العِلم أنّ هذا الخطأ قد حدث ويتوقف عن التحليل.
ومن الممكن بدلاً من ذلك الإعلان عن تبعية نوع سلسلة الأداة اختيارية، كما يلي:
bar_binary = rule(
...
toolchains = [
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
عندما يتعذّر حل نوع اختياري لسلسلة الأدوات، يستمر التحليل، وتكون نتيجة ctx.toolchains[""//bar_tools:toolchain_type"]
هي None
.
تكون دالة config_common.toolchain_type
هي الإعداد التلقائي إلزاميًا.
يمكن استخدام النماذج التالية:
- إليك أنواع سلاسل الأدوات الإلزامية:
toolchains = ["//bar_tools:toolchain_type"]
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type")]
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = True)]
- إليك أنواع سلاسل الأدوات الاختيارية:
toolchains = [config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False)]
bar_binary = rule(
...
toolchains = [
"//foo_tools:toolchain_type",
config_common.toolchain_type("//bar_tools:toolchain_type", mandatory = False),
],
)
ويمكنك مزج النماذج ومطابقتها في القاعدة نفسها أيضًا. ومع ذلك، إذا تم إدراج نوع سلسلة الأدوات نفسه عدّة مرات، سيُطبَّق النسخة الأكثر صرامة حيث يكون إلزاميًا أكثر صرامةً من الاختيار.
جوانب الكتابة التي تستخدم سلاسل الأدوات
يمكن للواجهات الوصول إلى واجهة برمجة تطبيقات سلسلة الأدوات نفسها، إذ يمكنك تحديد أنواع سلسلة الأدوات المطلوبة والوصول إلى سلاسل الأدوات من خلال السياق واستخدامها لإنشاء إجراءات جديدة باستخدام سلسلة الأدوات.
bar_aspect = aspect(
implementation = _bar_aspect_impl,
attrs = {},
toolchains = ['//bar_tools:toolchain_type'],
)
def _bar_aspect_impl(target, ctx):
toolchain = ctx.toolchains['//bar_tools:toolchain_type']
# Use the toolchain provider like in a rule.
return []
تحديد سلاسل الأدوات
لتحديد بعض سلاسل الأدوات لنوع معيّن من سلاسل الأدوات، تحتاج إلى ثلاثة أشياء:
قاعدة بلغة معيّنة تمثّل نوع الأداة أو مجموعة الأدوات يشير اصطلاح هذه القاعدة إلى لاحقة "_toolchain".
- ملاحظة: لا يمكن للقاعدة
\_toolchain
إنشاء أي إجراءات إنشاء. بل تجمع بدلاً من ذلك عناصر من القواعد الأخرى وتعيد توجيهها إلى القاعدة التي تستخدم سلسلة الأدوات. هذه القاعدة مسؤولة عن إنشاء كل إجراءات الإصدار.
- ملاحظة: لا يمكن للقاعدة
تمثل عدة أهداف من نوع القاعدة هذا إصدارات الأداة أو الأداة لأنظمة أساسية مختلفة.
بالنسبة إلى كل هدف من هذه الأهداف، يكون هناك هدف مرتبط بالقاعدة العامة
toolchain
، لتقديم البيانات الوصفية التي يستخدمها إطار عمل الأداة. يشير هدفtoolchain
هذا أيضًا إلىtoolchain_type
المرتبطة بسلسلة الأدوات هذه. وهذا يعني أنّ قاعدة_toolchain
معيّنة يمكن ربطها بأي قاعدة فيtoolchain_type
، وأنه في مثيلtoolchain
الذي يستخدم القاعدة_toolchain
فقط التي ترتبط بها القاعدة.toolchain_type
في ما يلي مثال خاص بالقاعدة الجارية، إليك تعريف لقاعدة bar_toolchain
. يحتوي المثال الذي نستخدمه على برنامج التجميع فقط، ولكن يمكن أيضًا تجميع أدوات أخرى مثل الرابط.
def _bar_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
barcinfo = BarcInfo(
compiler_path = ctx.attr.compiler_path,
system_lib = ctx.attr.system_lib,
arch_flags = ctx.attr.arch_flags,
),
)
return [toolchain_info]
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler_path": attr.string(),
"system_lib": attr.string(),
"arch_flags": attr.string_list(),
},
)
يجب أن تعرض القاعدة موفّر ToolchainInfo
، والذي يصبح الكائن الذي تسترده القاعدة المستهلَكة باستخدام ctx.toolchains
وتصنيف نوع سلسلة الأدوات. يمكن للسمة ToolchainInfo
، مثل struct
، إقران الأجهزة كأزواج عشوائية للحقول. يجب أن تكون مواصفات المواصفات التي تتم إضافتها إلى الحقل بدقة
ToolchainInfo
موثّقة بوضوح على نوع سلسلة الأدوات. في هذا المثال، تعرض القيم
موضوعة في كائن BarcInfo
لإعادة استخدام المخطط المحدّد أعلاه. قد يكون هذا النمط مفيدًا للتحقق من الصحة وإعادة استخدام الرمز.
يمكنك الآن تحديد استهدافات لبرامج تحويل barc
محددة.
bar_toolchain(
name = "barc_linux",
arch_flags = [
"--arch=Linux",
"--debug_everything",
],
compiler_path = "/path/to/barc/on/linux",
system_lib = "/usr/lib/libbarc.so",
)
bar_toolchain(
name = "barc_windows",
arch_flags = [
"--arch=Windows",
# Different flags, no debug support on windows.
],
compiler_path = "C:\\path\\on\\windows\\barc.exe",
system_lib = "C:\\path\\on\\windows\\barclib.dll",
)
أخيرًا، عليك إنشاء toolchain
تعريفات لهدفَي bar_toolchain
.
وتربط هذه التعريفات الاستهدافات بلغات معيّنة مع نوع سلسلة الأدوات، كما تقدّم معلومات القيود التي تحدّد Bazel عندما تكون سلسلة الأدوات مناسبة لنظام أساسي معيّن.
toolchain(
name = "barc_linux_toolchain",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_linux",
toolchain_type = ":toolchain_type",
)
toolchain(
name = "barc_windows_toolchain",
exec_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
target_compatible_with = [
"@platforms//os:windows",
"@platforms//cpu:x86_64",
],
toolchain = ":barc_windows",
toolchain_type = ":toolchain_type",
)
يشير استخدام بنية المسار النسبي أعلاه إلى أن التعريفات كلها في الحزمة نفسها، ولكن ليس هناك أي سبب لتعذُّر تضمين كل من نوع سلسلة الأدوات واستهدافات الأدوات الخاصة باللغة، واستهدافات تعريف toolchain
. حِزم منفصلة.
اطّلع على go_toolchain
للحصول على مثال من العالم الحقيقي.
سلاسل الأدوات وعمليات الضبط
هناك سؤال مهم لمؤلفي القواعد هو: عند تحليل هدف bar_toolchain
والإعدادات التي يراها وما عمليات النقل التي يجب استخدامها للاعتماديات؟ يستخدم المثال أعلاه سمات السلسلة، ولكن ماذا سيحدث لسلسلة أدوات أكثر تعقيدًا تعتمد على أهداف أخرى في مستودع Bazel؟
لنرى إصدارًا أكثر تعقيدًا من bar_toolchain
:
def _bar_toolchain_impl(ctx):
# The implementation is mostly the same as above, so skipping.
pass
bar_toolchain = rule(
implementation = _bar_toolchain_impl,
attrs = {
"compiler": attr.label(
executable = True,
mandatory = True,
cfg = "exec",
),
"system_lib": attr.label(
mandatory = True,
cfg = "target",
),
"arch_flags": attr.string_list(),
},
)
يختلف استخدام attr.label
عن القاعدة العادية،
ولكن يختلف معنى المَعلمة cfg
قليلاً.
تستخدم الاعتمادية من هدف (يُطلق عليه "الوالدَين") إلى سلسلة أدوات عبر دقة سلسلة الأدوات استخدام انتقال إعداد خاص يُسمى "نقل سلسلة الأدوات". يحافظ نقل سلسلة الأدوات على ضبط الإعدادات نفسه، باستثناء أنّه يفرض على النظام الأساسي للتنفيذ أن يكون مماثلاً لسلسلة الأدوات للمنصة الرئيسية (وإلا، يمكن أن تختار دقة سلسلة الأدوات لسلسلة الأدوات أي نظام تنفيذ، وليس بالضرورة أن يكون هو نفسه أحد الوالدين). ويتيح ذلك أن تكون أي تبعيات exec
لسلسلة الأدوات قابلة للتنفيذ أيضًا لإجراءات إنشاء الوالدَين. يتم إنشاء أي تبعيات لسلسلة أدوات تستخدم cfg =
"target"
(أو لا تحدّد cfg
، لأن "target" هي القيمة التلقائية) للنظام الأساسي المستهدف نفسه كمنصة رئيسية. ويسمح ذلك لقواعد سلسلة الأدوات بالمساهمة في كل من المكتبات (السمة system_lib
أعلاه) والأدوات (السمة compiler
) في قواعد الإصدار التي تحتاج إليها. ويتم ربط مكتبات النظام بالعنصر النهائي النهائي، وبالتالي يجب إنشاؤها لنظام التشغيل الأساسي نفسه، في حين أن المُجمِّع عبارة عن أداة تم استدعاؤها أثناء الإصدار، وتحتاج إلى أن تكون تعمل على.
التسجيل والإنشاء باستخدام سلاسل الأدوات
عند هذه النقطة، يتم تجميع كل الوحدات الأساسية، وتحتاج فقط إلى جعل سلاسل الأدوات متاحة لإجراء حل Bazel. ويمكن إجراء ذلك من خلال تسجيل سلسلة الأدوات، إما في ملف WORKSPACE
باستخدام
register_toolchains()
أو من خلال تمرير تصنيفات سلسلة الأدوات على سطر الأوامر
باستخدام العلامة --extra_toolchains
.
register_toolchains(
"//bar_tools:barc_linux_toolchain",
"//bar_tools:barc_windows_toolchain",
# Target patterns are also permitted, so you could have also written:
# "//bar_tools:all",
)
والآن، عند إنشاء هدف يعتمد على نوع سلسلة أدوات، سيتم اختيار سلسلة أدوات مناسبة استنادًا إلى المنصات المستهدفة والتنفيذ.
# my_pkg/BUILD
platform(
name = "my_target_platform",
constraint_values = [
"@platforms//os:linux",
],
)
bar_binary(
name = "my_bar_binary",
...
)
bazel build //my_pkg:my_bar_binary --platforms=//my_pkg:my_target_platform
سيلاحظ البازل أنه يتم إنشاء //my_pkg:my_bar_binary
باستخدام نظام أساسي يتضمّن @platforms//os:linux
، وبالتالي سيحلّ المرجع //bar_tools:toolchain_type
إلى //bar_tools:barc_linux_toolchain
.
سيؤدي هذا إلى إنشاء //bar_tools:barc_linux
ولكن ليس //bar_tools:barc_windows
.
دقة سلسلة الأدوات
بالنسبة إلى كل هدف يستخدم "سلاسل الأدوات"، تحدّد إجراءات حلّ سلسلة أدوات Bazel تبعيات سلسلة الأدوات الخرسانية الخاصة بالهدف. ويكون الإجراء عبارة عن مجموعة من أنواع سلسلة الأدوات المطلوبة، والنظام الأساسي المستهدف، وقائمة الأنظمة الأساسية للتنفيذ المتاحة، وقائمة سلاسل الأدوات المتاحة. وتُعتبر نتائجه سلسلة أدوات محدّدة لكل نوع من سلاسل الأدوات بالإضافة إلى منصّة تنفيذ محددة للهدف الحالي.
يتم جمع منصات التنفيذ وسلاسل الأدوات المتوفّرة من ملف WORKSPACE
عبر
register_execution_platforms
و
register_toolchains
.
وقد يتم أيضًا تحديد منصّات تنفيذ وسلاسل أدوات إضافية في
سطر الأوامر عبر
--extra_execution_platforms
و--extra_toolchains
.
يتم تضمين النظام الأساسي المضيف تلقائيًا كمنصّة تنفيذ متاحة.
يتم تتبّع الأنظمة الأساسية وسلاسل الأدوات المتاحة كقوائم مرتبة للتعريف، مع إعطاء الأفضلية للعناصر السابقة في القائمة.
في ما يلي خطوات حلّ المشكلة:
يتطابق عنصر
target_compatible_with
أوexec_compatible_with
مع منصّة إذا كانتconstraint_value
لكل منصّة ضمن القائمتين، يكون للمنصّة أيضًاconstraint_value
(أو بشكل صريح أو تلقائي).إذا كان النظام الأساسي يتضمّن عناصر
constraint_value
منconstraint_setting
التي لم تتم الإشارة إليها في الفقرة، لن تؤثر هذه السمات في المطابقة.إذا كان الهدف الذي يتم إنشاؤه يحدِّد
exec_compatible_with
السمة (يحدد تعريف القاعدةexec_compatible_with
الحجة )، وتجري فلترة قائمة الأنظمة الأساسية المتاحة للتنفيذ لإزالة أي منصّات لا تتطابق مع قيود التنفيذ.وبالنسبة إلى كل نظام أساسي للتنفيذ متاح، يمكنك ربط كل نوع من سلاسل الأدوات بأول سلسلة أدوات متاحة، إن وجدت، تكون متوافقة مع النظام الأساسي للتنفيذ والنظام الأساسي المستهدف.
يتم استبعاد أي منصة تنفيذ تعذّر العثور على سلسلة أدوات إلزامية متوافقة مع أحد أنواع سلسلة الأدوات الخاصة بها. ومن بين الأنظمة الأساسية المتبقية، تصبح المنصّة الأولى هي المنصّة المخصّصة لتنفيذ الاستهداف الحالي، وتصبح سلاسل الأدوات المرتبطة بها (إن توفّرت) بمثابة تبعيات للهدف.
يتم استخدام النظام الأساسي للتنفيذ المُختار لتنفيذ كل الإجراءات التي ينشئها الهدف.
وفي الحالات التي يمكن فيها إنشاء الهدف نفسه في عمليات ضبط متعددة (مثل وحدات المعالجة المركزية المختلفة) في الإصدار نفسه، يتم تطبيق إجراء الحل بشكل مستقل عن كل إصدار من الهدف.
إذا كانت القاعدة تستخدم مجموعات التنفيذ، تعمل كل مجموعة تنفيذ على حلّ سلسلة أدوات بشكل منفصل، وتحتوي كل مجموعة على منصة تنفيذ وسلسلة أدوات خاصة بها.
سلاسل أدوات تصحيح الأخطاء
إذا كنت تضيف دعم سلسلة الأدوات إلى قاعدة حالية، استخدِم العلامة --toolchain_resolution_debug=regex
. وعند استخدام سلسلة الأدوات، تقدّم العلامة نتائج مطوَّلة لأنواع سلسلة الأدوات أو أسماء الهدف التي تتطابق مع متغير التعبير العادي. ويمكنك استخدام .*
لإخراج جميع المعلومات. ستُخرج Bazel أسماء سلاسل الأدوات التي تتحقق منها وتتخطاها أثناء عملية حل المشكلة.
إذا كنت ترغب في معرفة تبعيات cquery
الواردة من درجة دقة الأداة، يُرجى استخدام العلامة --transitions
على cquery
:
# Find all direct dependencies of //cc:my_cc_lib. This includes explicitly
# declared dependencies, implicit dependencies, and toolchain dependencies.
$ bazel cquery 'deps(//cc:my_cc_lib, 1)'
//cc:my_cc_lib (96d6638)
@bazel_tools//tools/cpp:toolchain (96d6638)
@bazel_tools//tools/def_parser:def_parser (HOST)
//cc:my_cc_dep (96d6638)
@local_config_platform//:host (96d6638)
@bazel_tools//tools/cpp:toolchain_type (96d6638)
//:default_host_platform (96d6638)
@local_config_cc//:cc-compiler-k8 (HOST)
//cc:my_cc_lib.cc (null)
@bazel_tools//tools/cpp:grep-includes (HOST)
# Which of these are from toolchain resolution?
$ bazel cquery 'deps(//cc:my_cc_lib, 1)' --transitions=lite | grep "toolchain dependency"
[toolchain dependency]#@local_config_cc//:cc-compiler-k8#HostTransition -> b6df211