تحدّد القاعدة سلسلة من الإجراءات التي ينفذها Bazel على الإدخالات لإنشاء مجموعة من النتائج، والتي تتم الإشارة إليها في مقدّمي الخدمة من خلال القاعدة ودالة التنفيذ. على سبيل المثال، قد تقوم قاعدة C++ ثنائية بما يلي:
- اختَر مجموعة من ملفات المصدر
.cpp
(الإدخالات). - تشغيل
g++
على ملفات المصدر (إجراء). - أرجِع موفّر
DefaultInfo
مع الإخراج القابل للتنفيذ والملفات الأخرى لإتاحةها في وقت التشغيل. - عرض موفِّر
CcInfo
مع معلومات C++ الخاصة بالمجموعة التي تم جمعها من الهدف واعتمادياته.
من منظور"بازل"أصبح g++
ومكتبات C++ العادية مدخلات أيضًا
في هذه القاعدة. بصفتك كاتب قاعدة، يجب ألا تفكّر فقط في الإدخالات التي يقدّمها المستخدمون
في القاعدة، ولكن يجب أيضًا مراعاة جميع الأدوات والمكتبات المطلوبة لتنفيذ الإجراءات.
قبل إنشاء أي قاعدة أو تعديلها، تأكّد من أنك على مستوى عمليات إنشاء Bazel'. ومن المهم فهم المراحل الثلاث التي يتميز بها التصميم (التحميل والتحليل والتنفيذ). من المفيد أيضًا الاطّلاع على معلومات حول وحدات الماكرو لفهم الفرق بين القواعد ووحدات الماكرو. للبدء، يُرجى أولاً مراجعة البرنامج التعليمي للقواعد. بعد ذلك، استخدِم هذه الصفحة كمرجع.
وقد تم تضمين بعض القواعد في Bazel نفسها. تقدّم هذه القواعد المدمجة مع المحتوى، مثل
cc_library
وjava_binary
، بعض الدعم الأساسي للغات معيّنة.
ومن خلال تحديد قواعدك الخاصة، يمكنك إضافة دعم مشابه للغات والأدوات التي لا تتوافق مع Bazel بشكل أساسي.
يوفّر Bazel نموذجًا قابلاً للتوسع لكتابة القواعد باستخدام لغة
Starlark. تمت كتابة هذه القواعد في .bzl
ملف، يمكن تحميلها مباشرةً من ملفات BUILD
.
عند تحديد القاعدة الخاصة بك، عليك تحديد السمات التي تدعمها وكيفية إنشاء نتائجها.
تحدّد دالة <br class="#39;s implementation
سلوكها الدقيق أثناء
مرحلة
التحليل. لا تشغّل هذه الدالة أي
أوامر خارجية. وبدلاً من ذلك، تسجِّل الإجراءات التي سيتم استخدامها لاحقًا
خلال مرحلة التنفيذ لإنشاء مخرجات القاعدة، إذا كانت هناك حاجة إليها.
إنشاء قاعدة
في ملف .bzl
، استخدِم الدالة rule لتحديد قاعدة جديدة، واحفظ النتيجة في متغيّر عمومي. يحدّد الاستدعاء إلى rule
السمات ووظيفة التنفيذ:
example_library = rule(
implementation = _example_library_impl,
attrs = {
"deps": attr.label_list(),
...
},
)
يحدّد هذا العمود نوعًا من القواعد باسم example_library
.
يجب أيضًا أن يحدّد استدعاء rule
ما إذا كانت القاعدة تنشئ مخرجات
قابلة للتنفيذ (باستخدام executable=True
)، أو
اختبارًا قابلاً للتنفيذ على وجه التحديد (باستخدام test=True
). وإذا كانت القاعدة الثانية، هي قاعدة اختبار،
ويجب أن ينتهي اسم القاعدة بالبروتوكول _test
.
إنشاء مثيل مستهدف
يمكن تحميل القواعد واستدعاءها في ملفات BUILD
:
load('//some/pkg:rules.bzl', 'example_library')
example_library(
name = "example_target",
deps = [":another_target"],
...
)
لا تؤدي كل استدعاء إلى قاعدة إنشاء إلى عرض أي قيمة، إلا أن لها تأثيرًا جانبيًا في تحديد هدف. ويُطلق على هذه العملية إثبات القاعدة. ويحدّد هذا الاسم اسمًا للاستهداف الجديد وقيمًا للسمات الهدف's.
ويمكن أيضًا استدعاء القواعد من دوال Starlark وتحميلها في ملفات .bzl
.
يُطلق على وظائف Starlark التي تُطلق على قواعد الاستدعاء اسم وحدات الماكرو ستارلارك.
يجب استدعاء وحدات ماكرو Starlark في نهاية المطاف من ملفات BUILD
، ولا يمكن استدعاءها إلا خلال مرحلة التحميل، عند تقييم ملفات BUILD
لتحديد الأهداف المستهدفة.
السمات
السمة هي وسيطة للقاعدة. يمكن أن توفر السمات قيمًا معيّنة لتنفيذ target، أو يمكن أن تشير إلى أهداف أخرى، ما يؤدي إلى إنشاء رسم بياني للاعتماديات.
يتم تحديد السمات الخاصة بالقواعد، مثل srcs
أو deps
، من خلال تمرير خريطة من أسماء السمات إلى المخططات (التي يتم إنشاؤها باستخدام وحدة attr
) إلى المَعلمة attrs
في rule
.
تتم إضافة السمات الشائعة، مثل
name
وvisibility
، بشكلٍ ضمني إلى جميع القواعد. وتتم إضافة السمات الإضافية بشكلٍ ضمني إلى القواعد القابلة للتنفيذ والاختبار على وجه التحديد. لا يمكن تضمين السمات التي تتم إضافتها بشكل ضمني إلى قاعدة في القاموس الذي يتم تمريره إلى
attrs
.
سمات المهام التابعة
تحدّد القواعد التي تعالج رمز المصدر عادةً السمات التالية للتعامل مع أنواع التبعيات المختلفة:
- تحدِّد
srcs
ملفات المصدر التي تتم معالجتها من خلال إجراءات الهدف. في أغلب الأحيان، يحدّد مخطط السمات امتدادات الملفات المتوقّعة لنوع ملف المصدر الذي تعالجه القاعدة. تحدّد القواعد للغات التي تتضمّن ملفات عناوين بشكل عام سمةhdrs
منفصلة للعناوين التي يعالجها الهدف والمستهلكون. - يحدّد
deps
تبعيات الرمز لهدف. يجب أن يحدّد مخطط السمة مقدّمي الخدمة الذين يجب أن تقدّمهم هذه الارتباطات التابعة. (على سبيل المثال،cc_library
يوفرCcInfo
). - يحدّد
data
الملفات التي سيتم توفيرها في وقت التشغيل لأي ملف تنفيذي يعتمد على هدف. من المفترض أن يسمح ذلك بتحديد الملفات العشوائية.
example_library = rule(
implementation = _example_library_impl,
attrs = {
"srcs": attr.label_list(allow_files = [".example"]),
"hdrs": attr.label_list(allow_files = [".header"]),
"deps": attr.label_list(providers = [ExampleInfo]),
"data": attr.label_list(allow_files = True),
...
},
)
في ما يلي أمثلة على سمات المهام التابعة. تحدّد أي سمة تحدّد تصنيف إدخال (التي تم تعريفها باستخدام
attr.label_list
أو
attr.label
أو
attr.label_keyed_string_dict
) ارتباطيات من نوع معيّن
بين هدف والأهداف التي يتم إدراج تصنيفاتها (أو العناصر
Label
) في تلك السمة عند تحديد الهدف. من الممكن أن يتم التعامل مع المستودع، وربما المسار، لهذه التصنيفات
بالنسبة إلى الاستهداف المحدّد.
example_library(
name = "my_target",
deps = [":other_target"],
)
example_library(
name = "other_target",
...
)
في هذا المثال، other_target
هي اعتمادية على my_target
، وبالتالي يتم تحليل other_target
أولاً. يحدث خطأ إذا كانت هناك دورة في الرسم البياني للمهام التابعة.
السمات الخاصة والاعتماديات الضمنية
تنشئ سمة التبعية التي تحتوي على قيمة تلقائية اعتمادية ضمنية. وهذا الإجراء ضمني لأنّه جزء من الرسم البياني المستهدَف لا يحدِّده المستخدم في ملف BUILD
. ويمكن الاستفادة من العناصر التابعة الضمنية في ترميز ترميز بين قاعدة ما وأداة (اعتمادية على وقت الإنشاء، مثل العارض) نظرًا لأن المستخدم لا يهتم في معظم الأحيان بتحديد الأداة التي تستخدمها القاعدة. داخل دالة تنفيذ القاعدة، يتم التعامل معها بالطريقة نفسها التي يتم التعامل بها مع المهام التابعة الأخرى.
إذا أردت توفير اعتمادية ضمنية بدون السماح للمستخدم بتجاوز هذه القيمة، يمكنك جعل السمة خاصّة من خلال منحها اسمًا يبدأ بشرطة سفلية (_
)، ويجب أن تكون للسمات الخاصة قيم تلقائية. بشكلٍ عام، من المنطقي فقط استخدام السمات الخاصة للاعتمادات الضمنية.
example_library = rule(
implementation = _example_library_impl,
attrs = {
...
"_compiler": attr.label(
default = Label("//tools:example_compiler"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
},
)
في هذا المثال، يعتمد كل هدف من النوع example_library
ضمنيًا على برنامج التجميع //tools:example_compiler
. ويسمح هذا الإجراء لوظيفة التنفيذ في example_library
# بتنفيذ إجراءات تستدعي العارض، على الرغم من أن المستخدم لم يمرّر تصنيفه كإدخال. بما أنّ _compiler
هي سمة خاصة، سيتبيّن أن السمة ctx.attr._compiler
ستشير دائمًا إلى //tools:example_compiler
في جميع الأهداف من هذا النوع من القواعد. وبدلاً من ذلك، يمكنك تسمية السمة compiler
بدون شرطة سفلية والاحتفاظ بالقيمة التلقائية. ويسمح هذا الإجراء للمستخدمين باستبدال
ال برنامج تحويل مختلف إذا لزم الأمر، لكنه لا يتطلب أي الوعي بالملصق البرمجي's.
بشكل عام، تُستخدم العناصر التابعة الضمنية للأدوات المتوفرة في المستودع نفسه الذي يتم فيه تنفيذ القاعدة. إذا كانت الأداة مستمدة من منصة التنفيذ أو من مستودع مختلف بدلاً من ذلك، يجب أن تحصل القاعدة على تلك الأداة من سلسلة أدوات.
سمات النتائج
تشير سمات المخرجات، مثل attr.output
و
attr.output_list
، إلى ملف إخراج يُنشئه
الهدف. تختلف هذه السمات عن سمات الاعتمادية بطريقتين:
- وتحدّد أهداف ملفات المخرجات بدلاً من الإشارة إلى الأهداف المحددة في مكان آخر.
- تعتمد أهداف ملف الناتج على استهداف قاعدة مثيل، بدلاً من العكس.
عادةً، لا يتم استخدام سمات الإخراج إلا عندما تحتاج القاعدة إلى إنشاء مخرجات
بأسماء من تحديد المستخدم لا يمكن أن تستند إلى الاسم المستهدف. إذا كانت القاعدة تحتوي على سمة مخرجات واحدة، تُسمى عادةً out
أو outs
.
سمات الإخراج هي الطريقة المفضّلة لإنشاء نتائج مُحدَّدة مسبقًا، والتي يمكن أن تعتمد تحديدًا على سطر الأوامر أو تعتمد عليه.
وظيفة التنفيذ
تتطلب كل قاعدة دالة implementation
. يتم تنفيذ هذه الدوال بدقة في مرحلة التحليل وتحويل الرسم البياني للأهداف التي تم إنشاؤها في مرحلة التحميل إلى رسم بياني للإجراءات التي سيتم تنفيذها أثناء مرحلة التنفيذ. وبالتالي، لا يمكن لدوال التنفيذ قراءة الملفات أو كتابتها.
عادةً ما تكون دوال تنفيذ القاعدة خاصة (تُسمَّى بشرطة سفلية
شرطة). عادةً ما يُطلق على هذه القواعد اسم القاعدة نفسها، ولكن لاحقًا تُسمى
_impl
.
تستخدم دوال التنفيذ معلمة واحدة فقط، وهي سياق القاعدة، التي تحمل الاسم التقليدي ctx
. تعرض القائمة
مقدّمي الخدمات.
الأهداف
ويتم تمثيل العناصر التابعة في وقت التحليل ككائنات Target
. تحتوي هذه العناصر على providers التي تم إنشاؤها عند تنفيذ دالة التنفيذ target's.
تتضمّن السمة ctx.attr
حقولاً مطابقة لكل سمة تابعة
وتحتوي على عناصر Target
تمثّل كل اعتمادية مباشرة عبر هذه السمة. بالنسبة إلى سمات label_list
، إليك قائمة
Targets
. بالنسبة إلى سمات label
، تمثّل هذه السمة Target
أو None
واحدة.
يتم عرض قائمة بكائنات الموفر من خلال دالة تنفيذ الهدف:
return [ExampleInfo(headers = depset(...))]
ويمكن الوصول إلى هذه العناوين باستخدام ترميز الفهرس ([]
)، مع تحديد نوع موفّر الخدمة كمفتاح. يمكن أن يكون هؤلاء من مقدّمي الخدمات المخصّصين وقد تم تعريفهم في Starlark أو
من مقدّمي القواعد الأصلية متاحين كمتغيّرات عالمية من Starlark.
على سبيل المثال، إذا كانت القاعدة تأخذ ملفات العناوين من خلال السمة hdrs
وقدّمتها إلى إجراءات التجميع للمستخدم المستهدَف والمستهلكين، يمكنه جمعها على النحو التالي:
def _example_library_impl(ctx):
...
transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]
بالنسبة إلى النمط القديم الذي يتم فيه عرض struct
من دالة تنفيذ
target' بدلاً من قائمة من كائنات موفّر الخدمة:
return struct(example_info = struct(headers = depset(...)))
يمكن استرداد موفّري البيانات من الحقل المقابل للعنصر Target
:
transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]
لا يُنصح بهذا النمط بشدة ويجب نقل القواعد بعيدًا عنه.
ملفات
ويتم تمثيل الملفات من خلال عناصر File
. بما أنّ Bazel لا يُجري ملف I/O أثناء مرحلة التحليل، لا يمكن استخدام هذه العناصر لقراءة محتوى الملف أو كتابته مباشرةً. وبدلاً من ذلك، يتم تمريرها إلى دوال محاكية للإجراءات (راجِع ctx.actions
) لإنشاء أجزاء من الرسم البياني للإجراء.
يمكن أن يكون File
ملف مصدر أو ملفًا تم إنشاؤه. يجب أن يكون كل ملف يتم إنشاؤه ناتجًا عن إجراء واحد بالضبط. لا يمكن أن تكون ملفات المصدر ناتجة عن
أي إجراء.
بالنسبة إلى كل سمة اعتمادية، يحتوي الحقل المقابل في ctx.files
على قائمة بالمخرجات التلقائية لجميع العناصر التابعة عبر هذه السمة:
def _example_library_impl(ctx):
...
headers = depset(ctx.files.hdrs, transitive=transitive_headers)
srcs = ctx.files.srcs
...
تحتوي ctx.file
على File
واحدة أو None
واحدة لسمات الاعتماد التي تم تحديد مواصفاتها allow_single_file=True
.
تعمل السمة ctx.executable
بالطريقة نفسها التي يعمل بها ctx.file
، ولكنها تحتوي فقط على حقول لسمات التبعية التي تم ضبط مواصفاتها executable=True
.
الإفصاح عن النتائج
أثناء مرحلة التحليل، يمكن أن تعمل دالة تنفيذ القاعدة على إنشاء مخرجات.
بما أنّ جميع التصنيفات يجب أن تكون معروفة أثناء مرحلة التحميل، لا تحتوي هذه النتائج الإضافية على أي تصنيفات. يمكن إنشاء عناصر من File
للمخرجات باستخدام
ctx.actions.declare_file
و
ctx.actions.declare_directory
. وغالبًا ما تستند أسماء النتائج إلى اسم target's,
ctx.label.name
:
def _example_library_impl(ctx):
...
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
...
بالنسبة إلى المخرجات المُعلَنة مسبقًا، مثل تلك التي تم إنشاؤها
لسمات الناتج، يمكن استرداد عناصر File
بدلاً من ذلك
من الحقول المقابلة في ctx.outputs
.
الإجراءات
يوضّح الإجراء كيفية إنشاء مجموعة من النتائج من مجموعة من المدخلات، على سبيل المثال "run; gcc على hello.c واحصل على hello.o". عند إنشاء إجراء، لا يشغِّل Bazel الأمر على الفور. ويسجّل ذلك في رسم بياني للاعتماديات، لأن أي إجراء يمكن أن يعتمد على مخرجات إجراء آخر. على سبيل المثال، في C، يجب استدعاء الرابط بعد برنامج التجميع.
يتم تحديد الوظائف للأغراض العامة التي تنشئ إجراءات في ctx.actions
:
ctx.actions.run
، لتشغيل ملف تنفيذي.ctx.actions.run_shell
، لتشغيل أمر Shell.ctx.actions.write
، لكتابة سلسلة على ملف.ctx.actions.expand_template
، لإنشاء ملف من نموذج.
يمكن استخدام ctx.actions.args
لتجميع الوسيطات بكفاءة للإجراءات. تجنّب تجنّب التسوية حتى وقت التنفيذ:
def _example_library_impl(ctx):
...
transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
headers = depset(ctx.files.hdrs, transitive=transitive_headers)
srcs = ctx.files.srcs
inputs = depset(srcs, transitive=[headers])
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
args = ctx.actions.args()
args.add_joined("-h", headers, join_with=",")
args.add_joined("-s", srcs, join_with=",")
args.add("-o", output_file)
ctx.actions.run(
mnemonic = "ExampleCompile",
executable = ctx.executable._compiler,
arguments = [args],
inputs = inputs,
outputs = [output_file],
)
...
وتتخذ الإجراءات قائمة أو مجموعة من ملفات الإدخال وتُنشئ قائمة (غير فارغة) بملفات الإخراج. يجب أن تكون مجموعة ملفات الإدخال والإخراج معروفة خلال مرحلة التحليل. وقد يعتمد ذلك على قيمة السمات، بما في ذلك مقدّمي الخدمات من الاعتماديات، ولكن لا يمكن أن يعتمد ذلك على نتيجة التنفيذ. على سبيل المثال، إذا كان الإجراء الذي تستخدمه لتنفيذ الأمر zip، عليك تحديد الملفات التي تتوقّع أن تتم تضخّمها (قبل تشغيل فك ضغط الملف). ويمكن أن تؤدي الإجراءات التي تنشئ عددًا متغيرًا من الملفات داخليًا إلى لفّها في ملف واحد (مثل zip أو tar أو تنسيق أرشيف آخر).
يجب أن تسرد الإجراءات كل ملاحظاتهم. ويُسمح بإدخالات البيانات غير المُستخدَمة، ولكنها غير فعّالة.
يجب أن تنشئ الإجراءات كل نتائجها. قد يكتبون ملفات أخرى، ولكن أي شيء غير موجود في النتائج لن يكون متاحًا للمستهلكين. يجب كتابة جميع النتائج المُعلَنة من خلال إجراء.
يمكن مقارنة الإجراءات بالوظائف البحتة: يجب أن تعتمد هذه الإجراءات فقط على الإدخالات المُدخلة، وتجنّب الوصول إلى معلومات الكمبيوتر أو اسم المستخدم أو الساعة أو الشبكة أو أجهزة I/O (باستثناء البيانات المُدخلة ومخرجات الكتابة). ويُعدّ هذا الإجراء مهمًا لأنه سيتم تخزين النتائج مؤقتًا وإعادة استخدامها.
يحلّ Bazel العناصر التابعة له، ما سيحدّد الإجراءات التي يتم تنفيذها. يحدث خطأ إذا كانت هناك دورة في الرسم البياني للاعتمادية. ولا يضمن إنشاء إجراء تنفيذه، ويعتمد ذلك على ما إذا كانت مخرجاته مطلوبة للإصدار.
مقدّمو خدمة
مقدمو الخدمات هم أجزاء من المعلومات تكشفها القاعدة عن قواعد أخرى تعتمد عليها. ويمكن أن تشمل هذه البيانات ملفات الإخراج أو المكتبات أو المعلّمات لتمريرها في سطر الأوامر في أي أداة أو أي شيء آخر يجب أن يعرفه المستهلكون عنهم.
نظرًا لأن وظيفة تنفيذ القاعدة لا يمكنها سوى قراءة مقدّمي الخدمات من الاعتماديات الفورية المستهدفة، يجب أن تعيد القواعد توجيه أي معلومات من الاعتماديات المستهدفة التي يجب أن يعرفها المستهلكون المستهدفون، بشكل عام، من خلال تجميع ذلك في depset
.
يتم تحديد موفّري الهدف " " من خلال قائمة عناصر Provider
التي تعرضها دالة التنفيذ.
ويمكن أيضًا كتابة دوال التنفيذ القديمة بنمط قديم حيث تعرض دالة التنفيذ struct
بدلاً من قائمة كائنات موفّر الخدمة. لا يُنصح بهذا النمط بشدة ويجب نقل القواعد بعيدًا عنه.
المُخرجات التلقائية
المخرجات التلقائية في الهدف هي على سبيل المثال، يحتوي
هدف java_library
//pkg:foo
على foo.jar
كإخراج تلقائي، لذلك سيتم إنشاؤه من خلال الأمر bazel build //pkg:foo
.
يتم تحديد المخرجات التلقائية من خلال المعلمة files
لـ
DefaultInfo
:
def _example_library_impl(ctx):
...
return [
DefaultInfo(files = depset([output_file]), ...),
...
]
إذا لم يتم عرض DefaultInfo
من خلال تنفيذ قاعدة أو لم يتم تحديد معلَمة files
، سيتم ضبط DefaultInfo.files
تلقائيًا على جميع المخرجات المُعلَنة مسبقًا (بشكل عام، تلك التي يتم إنشاؤها بواسطة سمات
المخرجات).
يجب أن توفّر القواعد التي تنفّذ الإجراءات مخرجات تلقائية، حتى في حال لم يكن من المتوقع استخدام هذه النتائج مباشرةً. الإجراءات التي لا تقع في الرسم البياني للنتائج المطلوبة مطلوبة: إذا تم استخدام الناتج فقط من قِبل مستهلكين، لن يتم تنفيذ هذه الإجراءات عندما يتم إنشاء الهدف بشكل منفصل. ويؤدي ذلك إلى جعل عملية تصحيح الأخطاء أكثر صعوبة لأن إعادة إنشاء الهدف الذي يتعذّر تنفيذه فقط لن يؤدي إلى إعادة إظهار الخطأ.
ملفات التشغيل
ملفات التشغيل هي مجموعة من الملفات يستخدمها هدف وقت التشغيل (على عكس وقت الإنشاء). خلال مرحلة التنفيذ، ينشئ Bazel شجرة دليل تحتوي على روابط تشير إلى ملفات run. يؤدي ذلك إلى تشغيل بيئة البرنامج الثنائي حتى يتمكن من الوصول إلى ملفات التشغيل أثناء وقت التشغيل.
يمكن إضافة ملفات Run يدويًا أثناء إنشاء القاعدة.
يمكن إنشاء كائنات runfiles
باستخدام الطريقة runfiles
في سياق القاعدة، ctx.runfiles
وتمريرها إلى المعلمة runfiles
على DefaultInfo
. تتم إضافة مخرجات القواعد القابلة للتنفيذ بشكل ضمني إلى ملفات Run.
تحدّد بعض القواعد السمات، والتي تُسمى بشكل عام
data
، والتي تتم إضافة مخرجاتها إلى
أهداف و#39; runfiles. يجب أيضًا دمج ملفات التشغيل من data
، وكذلك من أي سمات قد توفّر رمزًا للتنفيذ النهائي، بشكلٍ عامsrcs
(الذي قد يحتوي على filegroup
هدف مرتبط بـ data
) وdeps
.
def _example_library_impl(ctx):
...
runfiles = ctx.runfiles(files = ctx.files.data)
transitive_runfiles = []
for runfiles_attr in (
ctx.attr.srcs,
ctx.attr.hdrs,
ctx.attr.deps,
ctx.attr.data,
):
for target in runfiles_attr:
transitive_runfiles.append(target[DefaultInfo].default_runfiles)
runfiles = runfiles.merge_all(transitive_runfiles)
return [
DefaultInfo(..., runfiles = runfiles),
...
]
مقدّمو خدمة مخصّصون
يمكن تحديد مقدّمي الخدمة باستخدام الدالة provider
لنقل المعلومات الخاصة بالقاعدة:
ExampleInfo = provider(
"Info needed to compile/link Example code.",
fields={
"headers": "depset of header Files from transitive dependencies.",
"files_to_link": "depset of Files from compilation.",
})
ويمكن لدوال تنفيذ القاعدة إنشاء مثيلات موفّر الخدمة وعرضها:
def _example_library_impl(ctx):
...
return [
...
ExampleInfo(
headers = headers,
files_to_link = depset(
[output_file],
transitive = [
dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
],
),
)
]
إعداد مخصص لمقدمي الخدمة
من الممكن حماية مثيل موفر الخدمة من خلال المعالجة المسبقة وعملية التحقق المخصصة. ويمكن استخدام هذا الإجراء للتأكّد من أنّ جميع مثيلات مقدّمي الخدمة تمتثل لثوابت معيّنة، أو لمنح المستخدمين واجهة برمجة تطبيقات أكثر وضوحًا من أجل الحصول على مثيل.
ويتم ذلك من خلال تمرير استدعاء init
إلى الدالة
provider
. إذا تم توفير معاودة الاتصال هذه،
فيتغير نوع العرض provider()
للسمة كقيمتين: رمز موفّر الخدمة الذي يمثل قيمة العرض العادية عند عدم استخدام init
، و"أداة إنشاء ":
في هذه الحالة، عندما يتم استدعاء رمز موفّر الخدمة، بدلاً من عرض مثيل جديد مباشرةً، سيعيد توجيه الوسيطات إلى استدعاء init
. يجب أن تكون قيمة الإرجاع (callback's) عبارة عن أسماء حقول تعيين (إملاء) في القيم.
ويتم استخدام هذا الإعداد لإعداد حقول المثيل الجديد. تجدر الإشارة إلى أن معاودة الاتصال قد تحتوي على أي توقيع، وإذا لم تتطابق الوسيطات مع التوقيع، يتم الإبلاغ عن خطأ كما لو تم استدعاء معاودة الاتصال مباشرة.
وعلى طرف النقيض، ستتجاوز دالة الإنشاء الأولية استدعاء init
.
يستخدِم المثال التالي init
للمعالجة المُسبَقة والتحقق من الوسيطات:
# //pkg:exampleinfo.bzl
_core_headers = [...] # private constant representing standard library files
# It's possible to define an init accepting positional arguments, but
# keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
if not files_to_link and not allow_empty_files_to_link:
fail("files_to_link may not be empty")
all_headers = depset(_core_headers, transitive = headers)
return {'files_to_link': files_to_link, 'headers': all_headers}
ExampleInfo, _new_exampleinfo = provider(
...
init = _exampleinfo_init)
export ExampleInfo
بعد ذلك، يمكن تنفيذ القاعدة على الفور من خلال موفّر الخدمة كما يلي:
ExampleInfo(
files_to_link=my_files_to_link, # may not be empty
headers = my_headers, # will automatically include the core headers
)
يمكن استخدام دالة الإنشاء الأولية لتحديد وظائف المصنع الأصلي البديلة التي لا تمر بالمنطق init
. على سبيل المثال، في exampleinfo.bzl يمكننا تحديد:
def make_barebones_exampleinfo(headers):
"""Returns an ExampleInfo with no files_to_link and only the specified headers."""
return _new_exampleinfo(files_to_link = depset(), headers = all_headers)
وعادةً ما يتم ربط أداة الإنشاء الأولية بمتغيّر يبدأ اسمه بشرطة سفلية (_new_exampleinfo
أعلاه)، بحيث لا يمكن لرمز المستخدم تحميله لتحميله وإنشاء مثيلات موفّرة عشوائية.
هناك استخدام آخر في init
وهو منع المستخدم من طلب رمز موفّر الخدمة بالكامل، وإلزامه باستخدام دالة الإعدادات الأصلية بدلاً من ذلك:
def _exampleinfo_init_banned(*args, **kwargs):
fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")
ExampleInfo, _new_exampleinfo = provider(
...
init = _exampleinfo_init_banned)
def make_exampleinfo(...):
...
return _new_exampleinfo(...)
القواعد القابلة للتنفيذ وقواعد الاختبار
تحدّد القواعد القابلة للتنفيذ الأهداف التي يمكن استدعاؤها من خلال أمر bazel run
.
قواعد الاختبار هي نوع خاص من القواعد التنفيذية التي يمكن أيضًا استدعاء أهدافها
عن طريق أمر bazel test
. يتم إنشاء القواعد التنفيذية والاختبارة
عن طريق ضبط الوسيطة executable
أو
test
المناسبة على True
في المكالمة إلى rule
:
example_binary = rule(
implementation = _example_binary_impl,
executable = True,
...
)
example_test = rule(
implementation = _example_binary_impl,
test = True,
...
)
يجب أن تحتوي قواعد الاختبار على أسماء تنتهي بالأرقام _test
. (غالبًا ما تنتهي أسماء target التجريبية
_test
عادةً، ولكن هذا الإجراء غير ضروري). يجب ألا تحتوي القواعد غير التجريبية
على هذه اللاحقة.
يجب أن ينتج عن كلا النوعَين من القواعد ملف إخراج قابل للتنفيذ (قد يتم أو لا يتم تأكيده مسبقًا) سيتم استدعاءه من خلال الأمرَين run
أو test
. لإبلاغ
Bazel بمخرجات قاعدة معيّنة لاستخدامها كملف تنفيذي هذا، عليك تمريرها كوسيطة
executable
لمقدّم خدمة DefaultInfo
عاد. تتم إضافة executable
إلى المُخرجات التلقائية للقاعدة (لذلك
لا تحتاج إلى تمرير ذلك إلى كل من executable
وfiles
). تتم إضافته ضمنيًا إلى runfiles:
def _example_binary_impl(ctx):
executable = ctx.actions.declare_file(ctx.label.name)
...
return [
DefaultInfo(executable = executable, ...),
...
]
يجب أن يؤدي الإجراء الذي يُنشئ هذا الملف إلى ضبط وحدة البت القابلة للتنفيذ على الملف. بالنسبة إلى إجراء ctx.actions.run
أو ctx.actions.run_shell
، يجب أن يتم ذلك باستخدام الأداة الأساسية التي تم استدعاءها من خلال الإجراء. للإجراء ctx.actions.write
، يُرجى تمرير is_executable=True
.
تتضمّن السلوك القديم للقواعد التنفيذية مخرجات خاصة مُعلَنة عن ctx.outputs.executable
. ويُعد هذا الملف بمثابة ملف تنفيذي تلقائي إذا لم تحدّد ملفًا باستخدام DefaultInfo
، ولا يجب استخدامه بخلاف ذلك. تم إيقاف آلية الإخراج هذه لأنها لا تتيح تخصيص اسم الملف التنفيذي في وقت التحليل.
اطّلع على أمثلة لكل من القاعدة القابلة للتنفيذ وقاعدة الاختبار.
تتضمّن القواعد التنفيذية وقواعد الاختبار سمات إضافية يتمّ تضمينها ضمنيًا، بالإضافة إلى السمات التي تمّت إضافتها إلى جميع القواعد. لا يمكن تغيير الإعدادات التلقائية للسمات المضافة بشكلٍ ضمني، ولكن يمكن معالجة هذا الأمر من خلال لفّ قاعدة خاصة في ماكرو لتطبيق Starlark والذي يغيّر الإعداد التلقائي:
def example_test(size="small", **kwargs):
_example_test(size=size, **kwargs)
_example_test = rule(
...
)
موقع ملفات التشغيل
عند تنفيذ هدف قابل للتنفيذ باستخدام bazel run
(أو test
)، يكون جذر
دليل الملفات مجاورًا للملف التنفيذي. ترتبط المسارات بما يلي:
# Given executable_file and runfile_file:
runfiles_root = executable_file.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
runfiles_root, workspace_name, runfile_path)
يتطابق المسار إلى File
ضمن دليل تنفيذ الملفات مع
File.short_path
.
يقع البرنامج الثنائي الذي تم تنفيذه مباشرةً من قِبل bazel
بجانب جذر الدليل runfiles
. أما البرامج الثنائية التي تُسمى من فملفات التشغيل لا يمكنها' إنشاء الافتراض نفسه. للحد من هذه المشكلة، يجب أن توفّر كل البرامج الثنائية طريقة
لقبول جذر الجذر في الملفات بصفته معلَمة باستخدام وسيطة/وسيطة
أمر/بيئة أو بيئة. ويؤدي ذلك إلى السماح للبرامج الثنائية بتمرير جذر ملفات التشغيل الأساسية الصحيح إلى البرامج الثنائية التي تطلبها. في حال عدم ضبط ذلك، يمكن أن يخمّن البرنامج الثنائي أنّه كان أول برنامج ثنائي يطلبه ويبحث عن دليل تنفيذ الملفات المجاور.
المواضيع المتقدمة
طلب ملفات الإخراج
يمكن أن يحتوي الاستهداف الواحد على عدة ملفات إخراج. عند تشغيل الأمر bazel build
، تُعتبر بعض نتائج الأهداف المحدّدة للأمر طلبًا. لا ينشئ Bazel هذه الملفات المطلوبة إلا والملفات التي يعتمد عليها
بشكل مباشر أو غير مباشر. (في الرسم البياني للإجراءات، لا ينفّذ تطبيق Bazel سوى الإجراءات التي يمكن الوصول إليها كاعتماديات مؤقتة على الملفات المطلوبة).
بالإضافة إلى المخرجات التلقائية، يمكن طلب أي إخراج مُعلَن عنه مسبقًا في سطر الأوامر. يمكن أن تحدّد القواعد المُخرجات المُعلَنة مسبقًا من خلال سمات المخرجات. في هذه الحالة، يختار المستخدم بشكل صريح التصنيفات للمخرجات عند إنشاء قاعدة جديدة. للحصول على عناصر File
لسمات الإخراج، استخدِم السمة المقابلة ctx.outputs
. يمكن أن تُحدِّد القواعد أيضًا النتائج المُحدَّدة مسبقًا استنادًا إلى الاسم المستهدَف أيضًا، ولكن تم إيقاف هذه الميزة.
بالإضافة إلى النتائج التلقائية، هناك مجموعات نتائج، وهي مجموعات من ملفات الإخراج التي قد يتم طلبها معًا. ويمكن طلب تنفيذ هذه الإجراءات من خلال
--output_groups
. على سبيل المثال، إذا كان الهدف //pkg:mytarget
من نوع القاعدة الذي يتضمّن مجموعة إخراج debug_files
، يمكن إنشاء هذه الملفات من خلال تشغيل bazel build //pkg:mytarget
--output_groups=debug_files
. بما أنّ النتائج غير المُعلَن عنها لا تتضمّن تصنيفات،
لا يمكن طلبها إلا من خلال الظهور في المُخرجات التلقائية أو في مجموعة
مخرجات.
يمكن تحديد مجموعات النتائج باستخدام موفِّر
OutputGroupInfo
. لاحظ أنّه على عكس العديد من مقدّمي الخدمة المضمّنين، يمكن لـ OutputGroupInfo
استخدام معلَمات بأسماء عشوائية لتحديد مجموعات إخراج بهذا الاسم:
def _example_library_impl(ctx):
...
debug_file = ctx.actions.declare_file(name + ".pdb")
...
return [
DefaultInfo(files = depset([output_file]), ...),
OutputGroupInfo(
debug_files = depset([debug_file]),
all_files = depset([output_file, debug_file]),
),
...
]
وعلى عكس معظم مقدّمي الخدمة، يمكن عرض OutputGroupInfo
من خلال كلٍّ من الجانب وهدف القاعدة الذي يتم تطبيق هذا الجانب عليه، طالما أنّهما لا يحدّدان مجموعات النتائج نفسها. في هذه الحالة، يتم دمج مقدّمي الخدمة الناتجين.
تجدر الإشارة إلى أنه يجب عدم استخدام OutputGroupInfo
بوجهٍ عام لنقل أنواع معيّنة من الملفات من هدف إلى إجراءات المستهلكين. حدِّد مقدّمي الخدمات المحدّدين للقواعد بدلاً من ذلك.
الإعدادات
لنفترض أنّك تريد إنشاء برنامج ثنائي C++ لبنية مختلفة. وقد يكون التصميم معقدًا ويشتمل على خطوات متعدّدة. يجب تشغيل بعض البرامج الثنائية المتوسطة، مثل العارضين وبرامج إنشاء الرموز، على النظام الأساسي للتنفيذ (الذي يمكن أن يكون مضيفك أو جهة تنفيذ عن بُعد). يجب إنشاء بعض البرامج الثنائية مثل الناتج النهائي للبنية المستهدفة.
ولهذا السبب، ابتكرت شركة Bazel مفهومًا وهي: "config&"الانتقالات. تمّ تصميم أعلى الأهداف (الأهداف المطلوبة في سطر الأوامر) ضمن الإعدادات "target" . بينما تم إنشاء الأدوات التي ينبغي تشغيلها على منصة التنفيذ في عملية ضبط "exec" . قد تنشئ القواعد إجراءات مختلفة بناءً على الإعدادات، على سبيل المثال لتغيير بنية وحدة المعالجة المركزية (CPU) التي يتم تمريرها إلى برنامج التجميع. في بعض الحالات، قد تحتاج المكتبة نفسها إلى الإعدادات المختلفة. وفي حال حدوث ذلك، سيتم تحليله وربما إتمامه عدة مرات.
تعمل Bazel تلقائيًا على إنشاء تبعيات target' في الإعدادات نفسها المستهدفة، أي بعبارة أخرى بدون عمليات انتقال. عندما تكون الاعتمادية هي أداة مطلوبة للمساعدة في إنشاء الهدف، يجب أن تحدد السمة المقابلة انتقالاً إلى إعداد تنفيذ. ويؤدي ذلك إلى إنشاء الأداة وجميع تبعياتها لإنشاء منصة التنفيذ.
بالنسبة إلى كل سمة اعتمادية، يمكنك استخدام cfg
لتحديد ما إذا كان يجب إنشاء التبعيات
في الإعدادات نفسها أو الانتقال إلى إعداد exe.
إذا كانت سمة الاعتمادية تحتوي على العلامة executable=True
، يجب ضبط السمة cfg
بشكل صريح. وهذا للحماية من إنشاء أداة عن طريق الخطأ لعملية الضبط غير الصحيحة.
راجع المثال
وبوجهٍ عام، يمكن أن تستخدم المصادر والمكتبات التابعة والملفات التنفيذية التي تكون مطلوبة في وقت التشغيل الإعدادات نفسها.
يجب إنشاء الأدوات التي تم تنفيذها كجزء من عملية الإنشاء (مثل برامج التجميع أو أدوات إنشاء الرموز) لإعداد exec. في هذه الحالة، حدِّد السمة cfg="exec"
في السمة.
وبخلاف ذلك، يجب إنشاء ملفات التنفيذية المستخدمة في وقت التشغيل (مثل كجزء من اختبار)
للضبط المستهدف. في هذه الحالة، حدِّد السمة cfg="target"
في السمة.
لا ينفّذ cfg="target"
أي شيء في الواقع، فهو يُعد قيمة ضئيلة لمساعدة مصممي القواعد على أن يكونوا صريحين بشأن نواياهم. وعند استخدام executable=False
،
وهذا يعني أن cfg
اختياري، يجب ألّا يتم ضبط هذا الإعداد إلا عندما يساعد على تسهيل القراءة.
يمكنك أيضًا استخدام cfg=my_transition
لاستخدام
عمليات النقل التي يحددها المستخدم، ما يتيح
لمؤلفي القواعد قدرًا كبيرًا من المرونة في تغيير الإعدادات، مع
التراجع عن
جعل الرسم البياني للإصدار أكبر وأقل فهمًا.
ملاحظة: في السابق، لم تفهم شركة Bazel مفهوم التنفيذ، بل كانت جميع إجراءات الإصدار تعمل على الجهاز المضيف. ولهذا السبب، هناك إعداد "host;host" واحد بالإضافة إلى "host"الانتقال يمكن استخدامه لإنشاء تبعية في إعداد المضيف. لا تزال العديد من القواعد تستخدِم عملية النقل "host;quot"لأدواتها، ولكن تم إيقاف ذلك حاليًا ويتم نقله لاستخدام عمليات النقل "exec" حيثما أمكن.
هناك العديد من الاختلافات بين عمليات الضبط &"host" و"exec":
- "host" isTerminal, "exec" is't: بعد اعتمادية الاعتماد على "host" الضبط، لا يُسمح باستخدام أي عمليات انتقال أخرى. يمكنك مواصلة عمليات نقل الإعدادات مرة أخرى عندما تكون في مرحلة ضبط "exe;".
- "host";صيغة متجانسة و"exe;exec" is't: لا يتوفر إعداد واحد إلا "host"quot;ولكن يمكن أن يكون هناك إعداد مختلف "exec" لكل نظام أساسي للتنفيذ.
- "host"تفترض أنّك تشغّل أدوات على نفس جهاز Bazel، أو على جهاز مشابه إلى حد كبير. لم يعد هذا صحيحًا: يمكنك تنفيذ إجراءات الإصدار على جهازك المحلي أو على منفّذ عن بُعد، ولا توجد أي ضمانات بأنّ الجهة التنفيذية عن بُعد هي وحدة المعالجة المركزية (CPU) ونظام التشغيل نفسه الذي تستخدمه الأداة المحلية.
يتم تطبيق تغييرات الخيارات نفسها على كل من عمليّتَي الضبط "exec" و&&;;quot;host"; (على سبيل المثال،
ضبط --compilation_mode
من --host_compilation_mode
وضبط --cpu
من
--host_cpu
، وما إلى ذلك). يبدأ الاختلاف في أنّ ضبط "host;host" يبدأ بالقيم التلقائية لجميع العلامات الأخرى، بينما يبدأ الضبط "exec"
بالقيم الحالية للعلامات، استنادًا إلى الإعدادات المستهدَفة.
أجزاء الإعداد
يمكن أن تصل القواعد إلى
أجزاء الضبط، مثل
cpp
وjava
وjvm
. ومع ذلك، يجب الإعلان عن جميع الأجزاء المطلوبة
لتجنُّب أخطاء الوصول:
def _impl(ctx):
# Using ctx.fragments.cpp leads to an error since it was not declared.
x = ctx.fragments.java
...
my_rule = rule(
implementation = _impl,
fragments = ["java"], # Required fragments of the target configuration
host_fragments = ["java"], # Required fragments of the host configuration
...
)
لا يوفّر ctx.fragments
سوى أجزاء الضبط الخاصة بالضبط المستهدف. إذا كنت تريد الوصول إلى أجزاء لضبط المضيف، استخدِم ctx.host_fragments
بدلاً من ذلك.
روابط ربط الملفات التي يتم تشغيلها
وعادةً ما يكون المسار النسبي للملف في العرض التدرّجي للملف مماثلاً للمسار النسبي لذلك الملف في العرض التدرّجي المصدر أو العرض التدرّجي الناتج للنتائج. إذا كانت هناك حاجة إلى اختلاف ما لسبب ما، يمكنك تحديد وسيطات root_symlinks
أو symlinks
. تُعدّ root_symlinks
مسارات تعيين للقاموس إلى الملفات، حيث ترتبط المسارات بجذر دليل runfiles. إن قاموس symlinks
هو نفسه، ولكن المسارات تبدأ مسبوقًا باسم مساحة العمل.
...
runfiles = ctx.runfiles(
root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
)
# Creates something like:
# sometarget.runfiles/
# some/
# path/
# here.foo -> some_data_file2
# <workspace_name>/
# some/
# path/
# here.bar -> some_data_file3
في حال استخدام symlinks
أو root_symlinks
، يجب الحرص على عدم ربط ملفَين مختلفَين بالمسار نفسه في شجرة الملفات. سيؤدي ذلك إلى تعذُّر عملية
الإنشاء مع ظهور خطأ يصف التعارض. لحلّ المشكلة، عليك تعديل
وسيطات ctx.runfiles
لإزالة التصادم. وسيتم إجراء هذا الفحص لأي
استهدافات تستخدِم قاعدتك، وكذلك أي نوع من الأهداف التي تعتمد على هذه
الاستهدافات. ويُعدّ هذا أمرًا خطيرًا على وجه الخصوص إذا كان من المحتمل أن يتم استخدام أداتك بشكل عابر،
وبالتالي يجب أن تكون أسماء الرموز المميزة فريدة في جميع ملفات أداة التشغيل وجميع تبعياتها.
تغطية الرمز
عند تنفيذ الأمر coverage
،
قد يحتاج الإصدار إلى إضافة أداة تغطية للأهداف المحدّدة. يجمع الإصدار أيضًا قائمة الملفات المصدر التي تم استخدامها. يتم التحكم في المجموعة الفرعية
من الأهداف التي يتم النظر فيها من خلال العلامة
--instrumentation_filter
.
يتم استبعاد الأهداف التجريبية ما لم يتم تحديد
--instrument_test_targets
.
إذا أضاف تنفيذ القاعدة أداة التغطية في وقت الإنشاء، يجب مراعاة ذلك في وظيفة التنفيذ. تعرض ctx.تغطيةage_tooled القيمة "صحيح" في وضع التغطية في حال كان من المفترض استخدام المصادر المستهدفة:
# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
# Do something to turn on coverage for this compile action
يمكن تطبيق المنطق الذي يجب تشغيله دائمًا في وضع التغطية (سواء تم تحديد المصادر المستهدفة أو بشكل خاص أم لا) على ctx.configuration.تغطيةage_enabled.
إذا كانت القاعدة تتضمن مصادر من المهام التابعة مباشرةً قبل التجميع (مثل ملفات العناوين)، قد تحتاج أيضًا إلى تفعيل قياس وقت التجميع إذا كانت المهام التابعة'، يجب استخدام المصادر:
# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
(ctx.coverage_instrumented() or
any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
# Do something to turn on coverage for this compile action
ويجب أن تقدّم القواعد أيضًا معلومات حول السمات ذات الصلة
بالتغطية مع موفّر خدمة InstrumentedFilesInfo
، حيث يتم إنشاؤها باستخدام
coverage_common.instrumented_files_info
.
يجب أن تسرد المعلمة dependency_attributes
instrumented_files_info
جميع سمات تبعية الوقت، بما في ذلك تبعيات الرمز مثل deps
واعتماديات البيانات مثل data
. من المفترض أن تُدرج المعلّمة source_attributes
سمات ملفات المصدر
في قاعدة البيانات إذا كان من المحتمل إضافة أداة قياس التغطية:
def _example_library_impl(ctx):
...
return [
...
coverage_common.instrumented_files_info(
ctx,
dependency_attributes = ["deps", "data"],
# Omitted if coverage is not supported for this rule:
source_attributes = ["srcs", "hdrs"],
)
...
]
في حال عدم عرض InstrumentedFilesInfo
، يتم إنشاء سمة تلقائية باستخدام كل سمة اعتماد غير تابعة للأداة لم يتم ضبطهاcfg
على "host"
أو "exec"
في مخطط السمات) في
dependency_attributes
. (هذا ليس سلوكًا مثاليًا، لأنّه يعرِض سمات مثل srcs
في dependency_attributes
بدلاً من source_attributes
، ولكنه يتجنّب الحاجة إلى ضبط التغطية السرية لجميع القواعد في سلسلة المهام التابعة).
إجراءات التحقّق
في بعض الأحيان، تحتاج إلى التحقّق من صحة بنية الإصدار، علمًا بأنّ المعلومات المطلوبة لإجراء التحقّق هذه لا تتوفّر سوى في العناصر (ملفات المصدر أو الملفات المنشأة). وبسبب توفّر هذه المعلومات ضمن العناصر، يتعذر على القواعد إجراء عملية التحقّق هذه في وقت التحليل لأنّ القواعد لا يمكنها قراءة الملفات. وبدلاً من ذلك، يجب أن تؤدي الإجراءات إلى عملية التحقّق هذه في وقت التنفيذ. وعندما تتعذّر عملية التحقّق، يتعذّر تنفيذ الإجراء، وبالتالي سيتم تجاهل الإصدار.
ومن أمثلة عمليات التحقق التي قد يتم إجراؤها هي التحليل الثابت والربط والاعتمادية والثبات والتحقّق من النمط.
يمكن أن تساعد إجراءات التحقّق أيضًا على تحسين أداء الإصدار من خلال نقل أجزاء من الإجراءات غير المطلوبة لإنشاء عناصر في إجراءات منفصلة. على سبيل المثال، إذا كان من الممكن فصل إجراء واحد يتم تجميعه وربطه مع إجراء تجميع وإجراء نَسب، يمكن عندئذٍ تنفيذ إجراء الربط كإجراء تحقُّق وتشغيله بالتزامن مع إجراءات أخرى.
لا تقدّم "إجراءات التحقّق هذه" أو "التحقق من الصحة" غالبًا أي شيء يتم استخدامه في مكان آخر من التصميم، لأنّها تحتاج فقط إلى التأكّد من آراءها. إلا أنّ ذلك يشكّل مشكلة، ولكن إذا لم يؤدِ إجراء التحقّق إلى عرض أي نتيجة يتم استخدامها في مكان آخر من المبنى، كيف يتم تنفيذ القاعدة؟ في السابق، كان النهج هو أن يكون إجراء التحقّق ناتجًا عن ظهور ملف فارغ، وإضافة هذا الناتج بشكلٍ زائف إلى مدخلات بعض الإجراءات المهمة الأخرى في الإصدار:
تنجح هذه الطريقة لأنّ Bazel ستعمل دائمًا على تنفيذ إجراء التحقّق عندما يتم تنفيذ إجراء التجميع، ولكن هناك بعض السلبيات:
إجراء التحقّق هو في المسار المهم للتصميم. بما أنّ Bazel يعتقد أنّ الناتج الفارغ مطلوب لتنفيذ إجراء التجميع، سيتم تنفيذ إجراء التحقّق أولاً، على الرغم من أن إجراء التجميع سيتجاهل الإدخال. ويؤدي ذلك إلى الحدّ من التوازي وإبطاء عملية إنشاء الإصدارات.
إذا كان من الممكن تنفيذ إجراءات أخرى في الإصدار بدلاً من إجراء التجميع، يجب إضافة النتائج الفارغة لإجراءات التحقّق إلى هذه الإجراءات أيضًا (على سبيل المثال، إخراج جرّة المصدر في
java_library
's). تحدث هذه المشكلة أيضًا إذا تمت إضافة إجراءات جديدة قد يتم تشغيلها بدلاً من إجراء التجميع لاحقًا، ويتم إيقاف مخرجات التحقّق الفارغة عن غير قصد.
يتمثل حل هذه المشاكل في استخدام مجموعة إخراج عمليات التحقق.
مجموعة إخراج عمليات التحقق
مجموعة نتائج التحقّق هي مجموعة إخراج مصمّمة للاحتفاظ بالمخرجات غير المستخدمة لإجراءات التحقّق، بحيث لا تحتاج إلى إضافتها إلى إدخالات الإجراءات الأخرى بشكلٍ زائف.
تُعدّ هذه المجموعة خاصة حيث يتم طلب النتائج دائمًا، بغض النظر عن قيمة العلامة --output_groups
، وبغض النظر عن طريقة الاعتماد على الاستهداف (على سبيل المثال، في سطر الأوامر أو الاعتمادية أو من خلال مخرجات الهدف الضمني). يُرجى ملاحظة أن التخزين المؤقت والتزايد العادي
لا يزالان ساريَين، وفي حال عدم تغيير المُدخلات في إجراء التحقّق ونجاح إجراء التحقّق من الصحة في السابق، لن يتم تنفيذ إجراء التحقّق.
لا يزال استخدام مجموعة النتائج هذه يتطلب أن تُخرج إجراءات التحقق بعض الملفات، حتى ولو كان فارغًا. قد يتطلب ذلك التفاف بعض الأدوات التي لا تعمل عادةً على إنشاء مخرجات بحيث يتم إنشاء ملف.
لا يتم تنفيذ إجراءات التحقّق من صحة الاستهداف في ثلاث حالات:
- الوقت الذي يعتمد عليه الاستهداف كأداة
- عندما يعتمد الهدف على الاعتمادية الضمنية (على سبيل المثال، سمة تبدأ بـ "&")
- عند تضمين الهدف في إعداد المضيف أو التنفيذ.
ويُفترض أيضًا أنّ هذه الأهداف تتضمّن إصدارات واختبارات منفصلة لاكتشاف أي حالات تعذُّر في عملية التحقّق.
استخدام مجموعة إخراج عمليات التحقق
تتم تسمية مجموعة إخراج عمليات التحقّق باسم _validation
ويتم استخدامها مثل أي مجموعة إخراج أخرى:
def _rule_with_validation_impl(ctx):
ctx.actions.write(ctx.outputs.main, "main output\n")
ctx.actions.write(ctx.outputs.implicit, "implicit output\n")
validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
ctx.actions.run(
outputs = [validation_output],
executable = ctx.executable._validation_tool,
arguments = [validation_output.path])
return [
DefaultInfo(files = depset([ctx.outputs.main])),
OutputGroupInfo(_validation = depset([validation_output])),
]
rule_with_validation = rule(
implementation = _rule_with_validation_impl,
outputs = {
"main": "%{name}.main",
"implicit": "%{name}.implicit",
},
attrs = {
"_validation_tool": attr.label(
default = Label("//validation_actions:validation_tool"),
executable = True,
cfg = "exec"),
}
)
تجدر الإشارة إلى أنّه لا تتم إضافة ملف ناتج التحقّق إلى DefaultInfo
أو إلى الإدخالات الخاصة بأي إجراء آخر. وسيستمر تنفيذ إجراء التحقّق من الأهلية لنوع القاعدة هذا إذا كان الهدف يعتمد على التصنيف، أو إذا كان أي من النتائج الضمنية المستهدفة يعتمد عليها بشكل مباشر أو غير مباشر.
من المهم عادةً ألا يتم إخراج مخرجات إجراءات التحقق إلا في مجموعة مخرجات التحقّق، ولا تتم إضافتها إلى مدخلات إجراءات أخرى، لأن ذلك قد يؤدي إلى هزم المكاسب المتوازية. ومع ذلك، ليس لدى Bazel حاليًا أي فحص خاص لفرض ذلك. لذلك، عليك اختبار عدم إضافة مخرجات إجراء التحقّق إلى إدخالات أي إجراءات في اختبارات قواعد Starlark. مثلاً:
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
def _validation_outputs_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
target = analysistest.target_under_test(env)
validation_outputs = target.output_groups._validation.to_list()
for action in actions:
for validation_output in validation_outputs:
if validation_output in action.inputs.to_list():
analysistest.fail(env,
"%s is a validation action output, but is an input to action %s" % (
validation_output, action))
return analysistest.end(env)
validation_outputs_test = analysistest.make(_validation_outputs_test_impl)
علامة إجراءات التحقّق
يتم التحكّم في تنفيذ إجراءات التحقّق من خلال علامة سطر الأوامر --run_validations
،
ويتم ضبطها تلقائيًا على"صحيح".
الميزات التي تم إيقافها
النتائج المُعلَنة مسبقًا موقوفة
هناك طريقتان متوقّفَتان لاستخدام النتائج المحدّدة مسبقًا:
تحدّد المعلّمة
outputs
منrule
عملية ربط بين أسماء سمات النتائج ونماذج السلسلة لإنشاء تصنيفات مخرجات مُعلَنة مسبقًا. أفضِّل استخدام مخرجات غير معلنة وإضافة مخرجات بشكل صريح إلىDefaultInfo.files
. استخدم تصنيف القاعدة target's كإدخال للقواعد التي تستهلك الناتج بدلاً من تصنيف تم إخراجه مسبقًا.بالنسبة إلى القواعد القابلة للتنفيذ، يشير
ctx.outputs.executable
إلى إخراج قابل للتنفيذ مُعلَن عنه مسبقًا يحمل الاسم نفسه كهدف القاعدة. حدِّد الإفصاح صراحةً عن الناتج، على سبيل المثال باستخدامctx.actions.declare_file(ctx.label.name)
، وتأكّد من أن الأمر الذي ينشئ الملف التنفيذي يحدد أذوناته للسماح بالتنفيذ. يجب تمرير المخرجات التنفيذية بشكل صريح إلى المَعلمةexecutable
فيDefaultInfo
.
ميزات ملفات الملفات لتجنبها
يتضمن النوعان ctx.runfiles
وrunfiles
مجموعة معقّدة من الميزات، يتم الاحتفاظ بالكثير منها لأسباب قديمة.
وتساعد الاقتراحات التالية في الحد من التعقيد:
تجنّب استخدام وضعَي
collect_data
وcollect_default
فيctx.runfiles
. تعمل هذه الأوضاع بشكلٍ ضمني على جمع ملفات التشغيل على حواف معيّنة للاعتماديات الثابتة بطرق بطرق مربكة. بدلاً من ذلك، يمكنك إضافة الملفات باستخدام المعلَمتَينfiles
أوtransitive_files
للرمزctx.runfiles
، أو من خلال دمج الملفات في عمليات التشغيل من التبعيات معrunfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
.تجنَّب استخدام
data_runfiles
وdefault_runfiles
لأداة الإنشاءDefaultInfo
. حدِّدDefaultInfo(runfiles = ...)
بدلاً من ذلك. يتم الاحتفاظ بالتمييز بين "default;quot; &"data"run; لإدارة الملفات لأسباب قديمة. على سبيل المثال، تضع بعض القواعد النتائج التلقائية فيdata_runfiles
، وليس فيdefault_runfiles
. بدلاً من استخدامdata_runfiles
، يجب أن تتضمن القواعد كلا المخرجات التلقائية ويتم دمجها فيdefault_runfiles
من السمات التي توفّر ملفات Run (غالبًاdata
).عند استرداد
runfiles
منDefaultInfo
(بشكل عام فقط لدمج ملفات التشغيل بين القاعدة الحالية واعتمادياتها)، استخدِمDefaultInfo.default_runfiles
، وليسDefaultInfo.data_runfiles
.
نقل البيانات من مقدّمي الخدمة القديمين
في السابق، كان موفّرو Bazel حقولاً بسيطة في الكائن Target
. وقد تم الوصول إليها باستخدام عامل تشغيل النقطة، وتم إنشاؤها من خلال وضع الحقل في بنية تعرضها دالة تنفيذ القاعدة.
تم إيقاف هذا النمط ويجب عدم استخدامه في الرمز الجديد. يمكنك الاطّلاع أدناه على المعلومات التي قد تساعدك في عملية نقل البيانات. تتجنّب آلية مقدّم الخدمة الجديد تضارب الأسماء. ويتيح أيضًا إخفاء البيانات من خلال طلب تنفيذ أي رمز يصل إلى مثال موفّر الخدمة لاسترداده باستخدام رمز مقدم الخدمة.
في الوقت الحالي، لا يزال موفّرو الخدمات القديم متاحين. يمكن أن تؤدي القاعدة إلى عرض كلٍّ من مقدّمي الخدمة القديمين والحديثين على النحو التالي:
def _old_rule_impl(ctx):
...
legacy_data = struct(x="foo", ...)
modern_data = MyInfo(y="bar", ...)
# When any legacy providers are returned, the top-level returned value is a
# struct.
return struct(
# One key = value entry for each legacy provider.
legacy_info = legacy_data,
...
# Additional modern providers:
providers = [modern_data, ...])
إذا كان dep
هو الكائن Target
الناتج لمثيل لهذه القاعدة، يمكن استرداد موفّري المحتوى ومحتواه على النحوَين dep.legacy_info.x
وdep[MyInfo].y
.
بالإضافة إلى providers
، يمكن أن تأخذ البنية المعروضة أيضًا العديد من الحقول الأخرى التي لها معنى خاص (وبالتالي لا تنشئ موفّرًا قديمًا مقابلاً):
تتوافق الحقول
files
وrunfiles
وdata_runfiles
وdefault_runfiles
وexecutable
مع حقول الاسم نفسهDefaultInfo
. ولا يُسمح بتحديد أي من هذه الحقول أثناء عرض موفِّر خدمةDefaultInfo
أيضًا.يحصل الحقل
output_groups
على قيمة بنية ويتوافق معOutputGroupInfo
.
في تصريحات القواعد في provides
، وفي بيانات
providers
الخاصة بسمات الاعتمادية، يتم تمرير مقدّمي الخدمة القديمين في صورة سلاسل، ويتم تمرير
مزوّدي الخدمة العصريين من خلال رمز *Info
. احرِص على التغيير من السلاسل إلى الرموز
عند نقل البيانات. بالنسبة إلى مجموعات القواعد المعقدة أو الكبيرة التي يصعب فيها تعديل كل القواعد آليًا، قد يكون لديك وقت أسهل في حال اتّباع تسلسل الخطوات التالي:
عدِّل القواعد التي تُنشئ مقدّم الخدمة القديم لإنشاء كل من مقدّمي الخدمة القديم والحالي، باستخدام البنية المذكورة أعلاه. بالنسبة إلى القواعد التي تذكر أنّهم يعرضون مقدّم الخدمة القديم، عليك تعديل هذا البيان ليتضمّن كلاهما مقدّمي الخدمة القديم والحالي.
عدِّل القواعد التي تستهلك مقدِّم الخدمة القديم بدلاً من ذلك. إذا كانت أي تصريحات عن السمات تتطلب موفّر الخدمة القديم، يجب تعديلها أيضًا لتطلب من مقدّم الخدمة الحديث بدلاً من ذلك. اختياريًا، يمكنك تضمين هذا العمل في الخطوة 1 من خلال تشجيع المستهلكين على قبول/طلب إما مقدّم الخدمة: إجراء اختبار لمعرفة ما إذا كان المزوّد القديم يستخدم
hasattr(target, 'foo')
، أو المزوّد الجديد باستخدامFooInfo in target
.إزالة مقدّم الخدمة القديم بالكامل من جميع القواعد.