الجوانب

تشرح هذه الصفحة أساسيات استخدام الجوانب وفوائدها، وتوفر أمثلة بسيطة ومتقدمة.

تسمح الجوانب زيادة الرسوم البيانية المستندة إلى الاعتماد على المعلومات والإجراءات الإضافية. في ما يلي بعض السيناريوهات النموذجية عندما تكون الجوانب مفيدة:

  • يمكن لتقنيات IDE التي تدمج Bazel استخدام جوانب لجمع معلومات عن المشروع.
  • يمكن أن تستفيد أدوات إنشاء الرموز من الجوانب المطلوب تنفيذها على مدخلاتها بطريقة لا حيادية. على سبيل المثال، يمكن لملفات BUILD تحديد تسلسل هرمي لتعريفات مكتبة protobuf، ويمكن أن تستخدم القواعد المحددة للغة جوانب لإرفاق إجراءات إنشاء رمز دعم Protobuf للغة معينة.

أساسيات العرض إلى الارتفاع

تقدّم ملفات BUILD وصفًا لرمز مصدر المشروع: ملفات المصدر التي تُعد جزءًا من المشروع، والعناصر التي ينبغي إنشاؤها من الملفات، وما تمثله الارتباطات من تلك الملفات، وما إلى ذلك. وتستخدم Bazel هذه المعلومات لتنفيذ إصدار، وهذا يعني أنّه يحدِّد مجموعة الإجراءات اللازمة لإنشاء العناصر (مثل تشغيل برنامج التجميع أو الرابط) وإجراءات التنفيذ. وتحقق بازل هذا من خلال إنشاء رسم بياني اعتمادي بين الأهداف وزيارة هذا الرسم البياني لجمع هذه الإجراءات.

جرِّب استخدام ملف BUILD التالي:

java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)

يُحدِّد ملف BUILD هذا الرسم البياني للاعتمادية الذي يظهر في الشكل التالي:

إنشاء رسم بياني

الشكل 1. رسم بياني لاعتمادية الملف BUILD.

يحلل البازل هذا الرسم البياني للاعتمادية من خلال استدعاء وظيفة تنفيذ للقاعدة المقابلة (في هذه الحالة "java_library") لكل استهداف في المثال أعلاه. وتنشئ وظائف تنفيذ القاعدة إجراءات تؤدي إلى إنشاء عناصر، مثل ملفات .jar، وتمرير المعلومات، مثل المواقع الجغرافية وأسماء هذه العناصر، إلى تبعيات هذه الأهداف في مقدّمي الخدمات.

وتتشابه "الجانب" مع القواعد في أنّها تحتوي على وظيفة تنفيذ تنشئ إجراءات وموفّري خدمة. ومع ذلك، فإن إمكانياتها تعتمد على الرسم البياني للاعتمادية عليها. يحتوي الجانب على عملية تنفيذ وقائمة بجميع السمات التي يتم نشرها عليه. فكِّر أيضًا في أحد الجوانب (أ) الذي يمتد على طول السمات المسماة &&quot؛deps". يمكن تطبيق هذا الجانب على الاستهداف المستهدف، مما يؤدي إلى عقدة تطبيق العرض أ(س). خلال تطبيقه، يتم تطبيق "الجانب أ" بشكل متكرّر على جميع الأهداف التي يشير إليها "س" في سمة "&&;;deps" (جميع السمات في قائمة نشر A's).

وبالتالي، فإن تنفيذ عملية واحدة من تطبيقات المحتوى على A

إنشاء رسم بياني مع العرض إلى الارتفاع

الشكل 2. إنشاء رسم بياني يضم الجوانب.

الحواف الوحيدة التي تم التظليل هي حوافها على طول السمات في مجموعة النشر، وبالتالي لم يتم تظليل حافة runtime_deps في هذا المثال. يتم عندئذ استدعاء وظيفة تنفيذ جانب إلى جميع العُقد في الرسم البياني المظلل بطريقة مماثلة لاستدعاء عمليات تنفيذ القواعد على عُقد الرسم البياني الأصلي.

مثال بسيط

يوضح هذا المثال كيفية طباعة الملفات المصدر بشكل متكرّر لقاعدة وجميع الارتباطات التي تحتوي على سمة deps. وهو يعرض عرضًا جانبيًا وتعريفًا للأوجه وكيفية استدعاء الجانب من سطر أوامر Bazel.

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

دعنا نقسم المثال إلى أجزائه ونفحص كل واحد منها على حدة.

تعريف العرض إلى الارتفاع

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
)

تتشابه تعريفات نسبة العرض إلى الارتفاع مع تعريفات القواعد، ويتم تحديدها باستخدام الدالة aspect.

وكما هو الحال مع القاعدة، يحتوي جانب على وظيفة تنفيذ تكون _print_aspect_impl في هذه الحالة.

attr_aspects هي قائمة بسمات القاعدة التي يتم بها نشر الجانب. في هذه الحالة، سيتم نشر الجانب بالسمة deps للقواعد التي يتم تطبيقها عليها.

هناك وسيطة أخرى شائعة لـ attr_aspects وهي ['*'] التي ستنشر جانب جميع سمات القاعدة.

التنفيذ الجانبية

def _print_aspect_impl(target, ctx):
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the files that make up the sources and
        # print their paths.
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                print(f.path)
    return []

تتشابه وظائف تنفيذ جوانب العرض مع وظائف تنفيذ القاعدة. تعرض المزوّدين، ويمكنهم إنشاء إجراءات، كما يمكن استخدام وسيطتين:

  • target: الهدف الذي يتم تطبيق الجانب عليه.
  • ctx: عنصر ctx الذي يمكن استخدامه للوصول إلى السمات وإنشاء مخرجات وإجراءات.

يمكن لدالة التنفيذ الوصول إلى سمات القاعدة المستهدفة من خلال ctx.rule.attr. يمكن لفحص مقدّمي الخدمة الذين يوفرهم الهدف الذي يتم تطبيقه عليهم (عبر الوسيطة target).

يجب أن يتم عرض قائمة بالجهات التي تقدّم البيانات. في هذا المثال، لا يقدّم الجانب أي شيء، لذا يعرض قائمة فارغة.

استدعاء الجانب باستخدام سطر الأوامر

إن أبسط طريقة لتطبيق أي جانب هي من سطر الأوامر باستخدام الوسيطة --aspects. على افتراض أنه تم تعريف الجانب أعلاه في ملف باسم print.bzl هذا:

bazel build //MyExample:example --aspects print.bzl%print_aspect

تطبيق print_aspect على الهدف example وجميع القواعد المستهدفة التي يمكن الوصول إليها بشكل متكرر عبر السمة deps.

تستخدم العلامة --aspects وسيطة واحدة، وهي إحدى مواصفات الجانب بتنسيق <extension file label>%<aspect top-level name>.

مثال متقدّم

يوضح المثال التالي استخدام جانب من قاعدة مستهدفة تحسب الملفات في الأهداف، ما قد يؤدي إلى فلترتها حسب الإضافة. ويعرض هذا الدليل طريقة استخدام مقدّم الخدمة لعرض القيم، وكيفية استخدام المعلّمات لتمرير وسيطة إلى عملية تنفيذ جانب، وكيفية استدعاء جانب من القاعدة.

ملف file_count.bzl:

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

ملف BUILD.bazel:

load('//:file_count.bzl', 'file_count_rule')

cc_library(
    name = 'lib',
    srcs = [
        'lib.h',
        'lib.cc',
    ],
)

cc_binary(
    name = 'app',
    srcs = [
        'app.h',
        'app.cc',
        'main.cc',
    ],
    deps = ['lib'],
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

تعريف العرض إلى الارتفاع

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

يوضح هذا المثال كيفية نشر الجانب من خلال السمة deps.

تحدّد السمة attrs مجموعة من السمات لأحد الجوانب. سمات الجانب العام من النوع string وتُسمى المعلمات. يجب أن تتضمن المعلمات السمةvalues عليها. يحتوي هذا المثال على معلمة تُسمى extension يُسمح بأن تحتوي على '*' أو 'h' أو 'cc' كقيمة.

ويتم الحصول على قيم المعلّمات لهذه السمة من سمة السلسلة التي تستخدم اسم القاعدة نفسه الذي يطلب الجانب (اطّلِع على تعريف file_count_rule). ولا يمكن استخدام الجوانب التي تتضمّن معلَمات من خلال سطر الأوامر نظرًا لعدم وجود بنية لتحديد المعلمات.

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

...
    attrs = {
        '_protoc' : attr.label(
            default = Label('//tools:protoc'),
            executable = True,
            cfg = "exec"
        )
    }
...

التنفيذ الجانبية

FileCountInfo = provider(
    fields = {
        'count' : 'number of files'
    }
)

def _file_count_aspect_impl(target, ctx):
    count = 0
    # Make sure the rule has a srcs attribute.
    if hasattr(ctx.rule.attr, 'srcs'):
        # Iterate through the sources counting files
        for src in ctx.rule.attr.srcs:
            for f in src.files.to_list():
                if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
                    count = count + 1
    # Get the counts from our dependencies.
    for dep in ctx.rule.attr.deps:
        count = count + dep[FileCountInfo].count
    return [FileCountInfo(count = count)]

تمامًا كما هو الحال في وظيفة تنفيذ القاعدة، تعرض وظيفة تنفيذ الجانب هيكلية لمقدمي الخدمات الذين يمكن الوصول إلى تبعياتهم.

في هذا المثال، يتم تعريف FileCountInfo باعتباره مقدّم خدمة ويحتوي على حقل واحد من count. من أفضل الممارسات تحديد حقول موفّر الخدمة بشكل واضح باستخدام السمة fields.

مجموعة الجهات المقدِّمة لتطبيق العرض A(X) هي اتحاد مقدّمي الخدمة الذين صدروا من تنفيذ قاعدة من أجل X المستهدفة ومن تنفيذ الجانب A. ويتم إنشاء مقدّمي الخدمة الذين ينشرون تنفيذ القاعدة ويجمدونهم قبل تطبيق الجوانب، ولا يمكن تعديلهم من جانب. ويكون ذلك خطأ إذا كان الهدف والجانب الذي يتم تطبيقه عليهما يوفران لمقدّم الخدمة من النوع نفسه، باستثناء OutputGroupInfo (الذي تم دمجه، طالما أن القاعدة والجوانب تحدّد مجموعات مخرجات مختلفة) وInstrumentedFilesInfo (التي يتم الحصول عليها من الجانب). وهذا يعني أن عمليات تنفيذ الجوانب قد لا تعرض DefaultInfo مطلقًا.

يتم تمرير المعلّمات والسمات الخاصة في سمات ctx. يشير هذا المثال إلى معلَمة extension ويحدّد الملفات المطلوب احتسابها.

بالنسبة إلى الموفّرين مكرري الزيارة، يتم استبدال قيم السمات التي يتم فيها نشر الجانب (من قائمة attr_aspects) بنتائج التطبيق من الجانب. على سبيل المثال، إذا كان الهدف (س) هو (ص) و(ع) في نقاط الانخفاض، سيكون ctx.rule.attr.deps (أ) (أ) (أ) (أ) و(أ) (ي)]. في هذا المثال، ctx.rule.attr.deps هي كائنات مستهدفة نتيجة نتائج تطبيق الجانب على الامتداد الأصلي الذي تم تطبيق الجانب عليه.

في المثال، يمكن للجانب الوصول إلى موفّر FileCountInfo من تبعيات target=# لتراكم إجمالي عدد الملفات الانتقالية.

استدعاء الجانب من قاعدة

def _file_count_rule_impl(ctx):
    for dep in ctx.attr.deps:
        print(dep[FileCountInfo].count)

file_count_rule = rule(
    implementation = _file_count_rule_impl,
    attrs = {
        'deps' : attr.label_list(aspects = [file_count_aspect]),
        'extension' : attr.string(default = '*'),
    },
)

يوضّح تنفيذ القاعدة كيفية الوصول إلى FileCountInfo من خلال ctx.attr.deps.

يوضح تعريف القاعدة كيفية تحديد معلمة (extension) ومنحها قيمة تلقائية (*). لاحظ أن وجود قيمة تلقائية لا تكون من بين 'cc', 'h' أو '*'، نظرًا للقيود المفروضة على المعلَمة ضمن تعريف الجانب.

استدعاء جانب من خلال قاعدة مستهدفة

load('//:file_count.bzl', 'file_count_rule')

cc_binary(
    name = 'app',
...
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

يوضح هذا كيفية تمرير المعلمة extension إلى الجانب عبر القاعدة. بما أن المعلَمة extension تحتوي على قيمة تلقائية في تنفيذ القاعدة، سيتم اعتبار extension معلَمة اختيارية.

عند إنشاء استهداف file_count، سيتم تقييم جانبه، وجميع الأهداف التي يمكن الوصول إليها بشكلٍ متكرر عبر deps.