إدارة البرامج التابعة الخارجية باستخدام Bzlmod

Bzlmod هو اسم رمز نظام الاعتماد الخارجي الجديد الذي تم إطلاقه في Bazel 5.0. وكان يهدف إلى معالجة العديد من المشاكل الصعبة في النظام القديم والتي لم يكن بالإمكان إصلاحها تدريجيًا. يمكنك الاطّلاع على قسم "بيان المشكلة" ضمن مستند التصميم الأصلي للحصول على المزيد من التفاصيل.

في Bazel 5.0، لا يتم تفعيل Bzlmod تلقائيًا. يجب تحديد العلامة --experimental_enable_bzlmod لتنفيذ ما يلي لكي تصبح سارية المفعول. كما يشير اسم العلامة، هذه الميزة تجريبية في الوقت الحالي، وقد تتغيّر واجهات برمجة التطبيقات والسلوكيات حتى يتم إطلاق الميزة رسميًا.

وحدات البازل

يتمحور نظام الاعتماد الخارجي القديم المستند إلى WORKSPACE حول المستودعات (أو المستودعات)، التي يتم إنشاؤها باستخدام قواعد المستودعات (أو قواعد الريبو). على الرغم من أن استخدام Reposs هو مفهوم مهم في النظام الجديد، إلا أن الوحدات هي الوحدات الأساسية للاعتماد.

الوحدة هي في الأساس مشروع Bazel ويمكن أن يحتوي على العديد من الإصدارات، حيث تنشر كل وحدة بيانات وصفية حول وحدات أخرى تعتمد عليها. وهذا التشابه مع المفاهيم المألوفة في أنظمة إدارة التبعية الأخرى: وهي قطعة أثرية وحزمة وصندوق تخزين. ، وحدة Go، إلخ.

تحدّد الوحدة ببساطة تبعياتها باستخدام أزواج name وversion، بدلاً من عناوين URL محدّدة في WORKSPACE. وبعد ذلك، يتم البحث عن التبعيات في سجلّ Bazel. بشكل تلقائي، سجلّ Bazel المركزي. في مساحة العمل، يتم تحويل كل وحدة بعد ذلك إلى تقرير.

MODULE.bazel

يتضمّن كل إصدار من كل وحدة ملف MODULE.bazel يشير إلى العناصر التابعة له والبيانات الوصفية الأخرى. وفي ما يلي مثال أساسي:

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

يجب وضع ملف MODULE.bazel في جذر دليل مساحة العمل (بجانب ملف WORKSPACE). وعلى عكس الملف WORKSPACE، لن تحتاج إلى تحديد تبعيات النقل. بدلاً من ذلك، يجب تحديد التبعيات المباشرة فقط، وتتم معالجة MODULE.bazel ملفات التبعيات لرصد التبعيات العابرة تلقائيًا.

يشبه ملف MODULE.bazel ملفات BUILD لأنه لا يتوافق مع أي شكل من أشكال تدفق التحكم. كما أنها تحظر load عبارة. تتوفّر الأوامر لملفات MODULE.bazel:

تنسيق الإصدار

تتّبع "بازيل" نظامًا بيئيًا متنوّعًا، وتعتمد مشاريعها على أنظمة نسخ مختلفة. الأكثر رواجًا حتى الآن هو SemVer، ولكن هناك أيضًا مشاريع بارزة تستخدم مخططات مختلفة مثل Appseil التي تتضمن إصداراتها المستندة إلى التاريخ، على سبيل المثال 20210324.2).

لهذا السبب، يعتمد تطبيق Bzlmod إصدار SemVer الأكثر هدوءًا، بشكل خاص، ما يسمح لأي عدد من تسلسلات الأرقام في جزء "الإصدار" من الإصدار (بدلاً من 3 بالضبط كما يصف SemVer: MAJOR.MINOR.PATCH). بالإضافة إلى ذلك، لا يتم فرض دلالات على الزيادات الرئيسية والثانوية وإصدارات التصحيح. (ومع ذلك، يمكنك الاطّلاع على مستوى التوافق للحصول على مزيد من التفاصيل حول كيفية الإشارة إلى التوافق مع الأنظمة القديمة). ولا يتم تعديل الأجزاء الأخرى من مواصفات SemVer، مثل الواصلة التي تشير إلى نسخة تجريبية.

دقة الإصدار

تُعد مشكلة تبعية المستوى الماسي عنصرًا أساسيًا في مساحة إدارة التبعية. لنفترض أن لديك الرسم البياني التابع التالي:

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

ما هو إصدار D الذي يجب استخدامه؟ لحل هذا السؤال، يستخدم Bzlmod خوارزمية Minim Version Select (MVS) التي تم إدخالها في نظام وحدة Go. وتفترض MVS أن جميع الإصدارات الجديدة من الوحدة تتوافق مع الأنظمة القديمة، وبالتالي تختار أعلى إصدار محدد يحدّده أي عنصر تابع (D 1.1 في المثال). ويُطلق عليها "الحد الأدنى" لأن D.1 هو الإصدار الأدنى الذي قد يستوفي متطلباتنا، حتى لو كان الإصدار D.1 أو إصدار أحدث، لا نحدّده. وهذا له فائدة إضافية تتمثل في أن اختيار النسخة عالي الدقة وقابل للتكرار.

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

مستوى التوافق

ملاحظة: إنّ افتراض MVS حول التوافق مع الأنظمة القديمة ممكن عمليًّا لأنه يتعامل ببساطة مع الإصدارات غير المتوافقة من الوحدة كوحدة منفصلة. بالنسبة إلى SemVer، يعني ذلك أن وحدتَي A وx.2 وx.x تُعتبر وحدتَين منفصلتَين، ويمكن أن تعملا معًا في الرسم البياني التابع الذي تم حلّ مشكلة التبعية. وبالتالي، أصبح هذا ممكنًا من خلال حقيقة ترميز النسخة الرئيسية في مسار الحزمة في Go، وبالتالي ليست هناك أي تعارضات في وقت التجميع أو وقت الربط.

لا نقدّم هذه الضمانات في Bazel. وبالتالي، نحتاج إلى طريقة تشير إلى الرقم "الإصدار الرئيسي" حتى نتمكّن من رصد الإصدارات غير المتوافقة إلى الخلف. يُطلق على هذا الرقم اسم مستوى التوافق، ويتم تحديده بواسطة كل إصدار وحدة في الأمر module(). من خلال هذه المعلومات، يمكننا التخلّص من الخطأ عند اكتشاف أن إصدارات من الوحدة نفسها ذات مستويات توافق مختلفة في الرسم البياني التابع الذي تم حلّه.

أسماء المستودعات

في Bazel، يكون لكل اعتمادية خارجية اسم مستودع. في بعض الأحيان، قد يتم استخدام الاعتمادية نفسها من خلال أسماء مستودعات مختلفة (مثلاً، @io_bazel_skylib و@bazel_skylib تعني "بازيل سكايلايب") أو قد يتم استخدام اسم المستودع تبعيات مختلفة في المشاريع المختلفة.

في Bzlmod، يمكن إنشاء المستودعات من خلال وحدات Bazel وإضافات الوحدات. لحلّ تعارضات أسماء المستودعات، نستعين بآلية ربط المستودعات في النظام الجديد. في ما يلي مفهومان مهمان:

  • اسم المستودع الأساسي: اسم المستودع الفريد العام لكل مستودع. سيكون هذا هو اسم الدليل الذي يوجد به المستودع.
    تم إنشاؤه على النحو التالي (تحذير: تنسيق الاسم الأساسي ليس واجهة برمجة تطبيقات يجب الاعتماد عليها، فهو عرضة للتغيير في أي وقت):

    • بالنسبة إلى وحدة وحدة Bazel: module_name.version
      (مثال). @bazel_skylib.1.0.3)
    • بالنسبة إلى إضافات الوحدات: module_name.version.extension_name.repo_name
      (مثال). @rules_cc.0.0.1.cc_configure.local_config_cc)
  • اسم المستودع المحلي: اسم المستودع المُراد استخدامه في ملفَّي BUILD و.bzl ضمن تقرير مسجّل. ويمكن أن يكون الاعتمادية نفسها أسماء محلية مختلفة لمستودعات مختلفة.
    يتم تحديد ذلك على النحو التالي:

    • بالنسبة إلى وحدة وحدة Bazel: module_name بشكلٍ تلقائي، أو الاسم المحدّد بواسطة السمة repo_name في bazel_dep.
    • لإعادة تخزين إضافة الوحدة: اسم المستودع الذي تم تقديمه عبر use_repo.

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

عمليات تقييد صارمة

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

يتم تنفيذ عمليات الضبط الصارمة استنادًا إلى ربط المستودعات. في الأساس، يتضمن تعيين المستودع لكل مستودع كل تبعياته المباشرة، ويكون أي مستودع آخر غير مرئي. يتم تحديد العناصر التابعة المرئية لكل مستودع على النحو التالي:

  • يمكن أن يرى Repal unit repo كل السلع المُقدَّمة في ملف MODULE.bazel من خلال bazel_dep وuse_repo.
  • يمكن أن يرى تقرير إضافة الوحدة جميع العناصر التابعة المرئية للوحدة التي توفّر الإضافة، بالإضافة إلى جميع مستودعات الجهاز الأخرى التي تم إنشاؤها بواسطة إضافة الوحدة نفسها.

قواعد بيانات المسجّلين

يكتشف تطبيق Bzlmod تبعيات ذلك من خلال طلب معلوماتها من قواعد بيانات من Bazel. إنّ قاعدة بيانات Bazel هي ببساطة قاعدة بيانات لوحدات Bazel. والنوع الوحيد المتوافق من قواعد بيانات المسجّلين هو سجلّ فهرس، وهو دليل محلي أو خادم HTTP ثابت يتّبع تنسيقًا معيّنًا. نخطِّط في المستقبل لإضافة دعم لقواعد بيانات المسجّلة المؤلفة من وحدة واحدة، والتي هي عبارة عن مستودعات git تحتوي على مصدر المشروع وتاريخه.

سجل الفهرس

إنّ سجلّ الفهرس هو دليل محلي أو خادم HTTP ثابت يحتوي على معلومات حول قائمة من الوحدات، بما في ذلك صفحتها الرئيسية ومقدّمو الخدمات وملف MODULE.bazel لكل إصدار وكيفية جلب مصدر كل منها. الإصدار. وتجدر الإشارة إلى أنّها لا تحتاج إلى عرض الأرشيفات المصدر نفسها.

يجب أن يتّبع سجلّ الفهرس التنسيق أدناه:

  • /bazel_registry.json: ملف JSON يحتوي على البيانات الوصفية للسجلّ وتحتوي حاليًا على مفتاح واحد فقط، mirrors، يحدد قائمة بالمرايا التي يمكن استخدامها للأرشيفات المصدر.
  • /modules: دليل يحتوي على دليل فرعي لكل وحدة في قاعدة بيانات المسجّلين هذه.
  • /modules/$MODULE: دليل يحتوي على دليل فرعي لكل إصدار من هذه الوحدة، بالإضافة إلى الملف التالي:
    • metadata.json: ملف JSON يحتوي على معلومات حول الوحدة، مع الحقول التالية:
      • homepage: عنوان URL للصفحة الرئيسية للمشروع.
      • maintainers: قائمة بكائنات JSON، يمثّل كل منها معلومات حافظ على الوحدة في قاعدة بيانات المسجّلين. لاحظ أن هذا ليس بالضرورة مختلفًا عن مؤلفي المشروع.
      • versions: قائمة بجميع إصدارات هذه الوحدة التي يمكن العثور عليها في قاعدة بيانات المسجّلين هذه.
      • yanked_versions: قائمة بالإصدارات المختلقة لهذه الوحدة. علمًا بأن هذا الأمر لا يصلح حاليًا، ولكن في المستقبل، سيتم تخطّي الإصدارات المختلَطة أو إصدار خطأ.
  • /modules/$MODULE/$VERSION: دليل يحتوي على الملفات التالية:
    • MODULE.bazel: الملف MODULE.bazel من إصدار الوحدة هذا.
    • source.json: ملف JSON يحتوي على معلومات حول طريقة جلب مصدر إصدار الوحدة هذا، مع الحقول التالية:
      • url: عنوان URL للأرشيف المصدر.
      • integrity: التحقّق من سلامة الموارد الفرعية في الأرشيف.
      • strip_prefix: بادئة الدليل المطلوب إزالتها عند استخراج الأرشيف المصدر.
      • patches: قائمة بالسلاسل، لكل منها اسم ملف تصحيح ليتم تطبيقه على الأرشيف المستخرج. وتقع ملفات التصحيح ضمن الدليل /modules/$MODULE/$VERSION/patches.
      • patch_strip: مثل الوسيطة --strip على تصحيح Unix.
    • patches/: دليل اختياري يحتوي على ملفات تصحيح.

سجل البازل المركزي

Bazel Central Registry (BCR) هو سجلّ فهرس يقع في registry.bazel.build. ويتم الاحتفاظ بنسخة احتياطية من المحتوى فيه من خلال تقرير GitHubbazelbuild/bazel-central-registry.

يتولى منتدى Bazel إدارة BCR. يمكن للمساهمين إرسال طلبات السحب. يُرجى الاطّلاع على سياسات وإجراءات المسجد المركزي في Bazel.

بالإضافة إلى اتّباع تنسيق سجلّ الفهرس العادي، تتطلب خدمة BCR ملف presubmit.yml لكل إصدار وحدة (/modules/$MODULE/$VERSION/presubmit.yml). يحدّد هذا الملف عددًا من الأهداف الأساسية للاختبار والاختبار التي يمكن استخدامها للتحقق من صحة إصدار هذه الوحدة. وتُستخدَم هذه التسلسلات من خلال أنابيب CIR في BCR لضمان إمكانية التشغيل التفاعلي بين الوحدات في BCR. .

اختيار قواعد بيانات المسجّلين

يمكن استخدام علامة Bazel المتكرّرة --registry لتحديد قائمة قواعد بيانات المسجّلين التي يجب طلب وحدات منها، لتتمكّن من إعداد مشروعك لجلب العناصر التابعة من جهة خارجية أو قاعدة بيانات المسجّلين الداخلية. وتحظى قواعد بيانات المسجّلين السابقة بالأولوية. للتيسير عليك، يمكنك وضع علامة --registry من العلامات في ملف .bazelrc من مشروعك.

إضافات الوحدات

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

يتم تحديد إضافات الوحدات في .bzl ملف، تمامًا مثل قواعد الإعادة أو وحدات ماكرو WORKSPACE. ولا يتم استدعاؤها مباشرةً؛ بدلاً من ذلك، يمكن لكل وحدة تحديد أجزاء من البيانات تُسمى العلامات للإضافات المطلوب قراءتها. وبعد الانتهاء من حل إصدار الوحدة، يتم تشغيل إضافات الوحدة. ويتم تشغيل كل إضافة مرة واحدة بعد درجة دقة الوحدة (التي لا تزال قبل حدوث أي تصميم فعليًا)، وتحصل على قراءة جميع العلامات التي تنتمي إليها عبر الرسم البياني التابع الكامل.

          [ A 1.1                ]
          [   * maven.dep(X 2.1) ]
          [   * maven.pom(...)   ]
              /              \
   bazel_dep /                \ bazel_dep
            /                  \
[ B 1.2                ]     [ C 1.0                ]
[   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
[   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
            \                  /
   bazel_dep \                / bazel_dep
              \              /
          [ D 1.4                ]
          [   * maven.dep(Z 1.4) ]
          [   * cargo.dep(Q 1.1) ]

في مثال الرسم البياني التابع أعلاه، تمثّل A 1.1 وB 1.2 وما إلى ذلك وحدات Bazel، ويمكنك اعتبار كل منها ملف MODULE.bazel. يمكن لكل وحدة تحديد بعض العلامات لإضافات الوحدات. في ما يلي بعض الإضافات لحزمة "Maven"، وبعضها محدد لـ "cargo". عند الانتهاء من الرسم البياني للتبعية هذا (على سبيل المثال، قد يكون لدى B 1.2 bazel_dep في الواقع على D 1.3 ولكن تمت ترقيته إلى D 1.4 بسبب C)، شغّلت الإضافات "maven" ويحصل على قراءة كل علامات maven.*، باستخدام المعلومات المتضمّنة فيها لتحديد المستودعات التي سيتم إنشاؤها. مثل الإضافة "شحن".

استخدام الإضافة

تتم استضافة الإضافات في وحدات Bazel نفسها، لذا لاستخدام إضافة في وحدتك، عليك أولاً إضافةbazel_dep في هذه الوحدة، ثم استدعاءuse_extension وظائف مدمجة لجعلها في النطاق. يمكنك اعتبار المثال التالي مقتطفًا من ملف MODULE.bazel لاستخدام إضافة "Maven" الافتراضية المُحدّدة في وحدة rules_jvm_external:

bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

بعد تطبيق الإضافة في النطاق، يمكنك استخدام بناء النقاط لتحديد العلامات لها. لاحظ أن العلامات تحتاج إلى اتباع المخطط المحدد في فئات العلامات المقابلة (راجع تعريف الإضافة أدناه). في ما يلي مثال لتحديد بعض علامات maven.dep وmaven.pom.

maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")

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

use_repo(
    maven,
    "org_junit_junit",
    guava="com_google_guava_guava",
)

المستودع الذي تم إنشاؤه بواسطة إضافة هو جزء من واجهة برمجة التطبيقات الخاصة بها، وبالتالي من العلامات التي حددتها، ينبغي أن تعرف أن الإضافة "maven" ستنشئ إعادة إصدار تسمى "org_junit_junit"، وأخرى تسمى "com_google_guava_guava" ". وباستخدام use_repo، يمكنك إعادة تسميتها اختياريًا في نطاق وحدتك، مثل "guava" هنا.

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

يتم تعريف إضافات الوحدات على نحو مماثل لقواعد إعادة التسجيل، وذلك باستخدام الدالة module_extension. ولكل منهما وظيفة تنفيذ؛ رغم أنّ قواعد الريبو تحتوي على عدد من السمات، فإنّ إضافات الوحدات تتضمّن عددًا من tag_classes، ولكل سمة عدد من السمات. تحدّد فئات العلامات مخططات للعلامات التي تستخدمها هذه الإضافة. في ما يلي مثال على الإضافة الافتراضية "المخفية" أعلاه:

# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
    implementation=_maven_impl,
    tag_classes={"dep": maven_dep, "pom": maven_pom},
)

توضح هذه التعريفات أنه يمكن تحديد علامات maven.dep وmaven.pom، باستخدام مخطط السمات المُحدد أعلاه.

تشبه وظيفة التنفيذ وحدة ماكرو WORKSPACE، باستثناء أنها تحصل على كائن module_ctx، ما يمنح حق الوصول إلى الرسم البياني التابعي وجميع العلامات ذات الصلة. يجب بعد ذلك على دالة التنفيذ استدعاء قواعد Repo لإنشاء repos:

# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
  coords = []
  for mod in ctx.modules:
    coords += [dep.coord for dep in mod.tags.dep]
  output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
  repo_attrs = process_coursier(output)
  [maven_single_jar(**attrs) for attrs in repo_attrs]

في المثال أعلاه، نراجع جميع الوحدات في الرسم البياني التابع (ctx.modules)، لكل وحدة منها كائن bazel_module يحتوي على tags. يعرض كل علامات maven.* في الوحدة. وبعد ذلك، نستدعي أداة CLI Courser للاتصال بخدمة Maven وإجراء الحل. أخيرًا، نستخدم نتيجة الدقة لإنشاء عدد من المستودعات، باستخدام قاعدة الافتراض الافتراضية maven_single_jar.