Bzlmod نام رمز سیستم جدید وابستگی خارجی است که در Bazel 5.0 معرفی شده است. این برای رسیدگی به چندین نقطه درد سیستم قدیمی که نمیتوانستند به صورت تدریجی برطرف شوند، معرفی شد. برای جزئیات بیشتر به بخش بیان مشکل سند طراحی اصلی مراجعه کنید.
در Bazel 5.0، Bzlmod به طور پیش فرض روشن نیست. برای اعمال موارد زیر باید پرچم --experimental_enable_bzlmod
مشخص شود. همانطور که از نام پرچم پیداست، این ویژگی در حال حاضر آزمایشی است. APIها و رفتارها ممکن است تا زمانی که این ویژگی به طور رسمی راه اندازی شود تغییر کند.
ماژول های بازل
سیستم وابستگی خارجی مبتنی بر WORKSPACE
قدیمی حول مخازن (یا مخازن )، ایجاد شده از طریق قوانین مخزن (یا قوانین مخزن ) متمرکز شده است. در حالی که مخازن هنوز یک مفهوم مهم در سیستم جدید هستند، ماژول ها واحدهای اصلی وابستگی هستند.
یک ماژول در اصل یک پروژه Bazel است که می تواند چندین نسخه داشته باشد، که هر کدام متادیتا را در مورد ماژول های دیگری که به آن وابسته است منتشر می کند. این شبیه به مفاهیم آشنا در سایر سیستمهای مدیریت وابستگی است: یک مصنوع Maven، یک بسته npm، یک جعبه Cargo، یک ماژول 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
پشتیبانی می کنند عبارتند از:
-
module
، برای تعیین ابرداده در مورد ماژول فعلی، از جمله نام، نسخه، و غیره. -
bazel_dep
، برای تعیین وابستگی های مستقیم به سایر ماژول های Bazel. - Overrides، که فقط می تواند توسط ماژول ریشه (یعنی نه توسط ماژولی که به عنوان یک وابستگی استفاده می شود) برای سفارشی کردن رفتار یک وابستگی مستقیم یا گذرا استفاده شود:
- دستورالعمل های مربوط به پسوند ماژول :
فرمت نسخه
Bazel دارای اکوسیستم متنوعی است و پروژه ها از طرح های نسخه سازی مختلفی استفاده می کنند. محبوبترین آنها SemVer است، اما پروژههای برجستهای نیز وجود دارند که از طرحهای مختلفی مانند Abseil استفاده میکنند، که نسخههای آن مبتنی بر تاریخ هستند، برای مثال 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 از الگوریتم Minimal Version Selection (MVS) معرفی شده در سیستم ماژول Go استفاده می کند. MVS فرض میکند که تمام نسخههای جدید یک ماژول با عقب سازگار هستند، و بنابراین به سادگی بالاترین نسخه مشخصشده توسط هر وابسته را انتخاب میکند (D 1.1 در مثال ما). این "حداقل" نامیده می شود زیرا D 1.1 در اینجا حداقل نسخه ای است که می تواند نیازهای ما را برآورده کند. حتی اگر D 1.2 یا جدیدتر وجود داشته باشد، آنها را انتخاب نمی کنیم. این مزیت افزوده است که انتخاب نسخه با وفاداری بالا و قابل تکرار است.
وضوح نسخه به صورت محلی بر روی دستگاه شما انجام می شود، نه توسط رجیستری.
سطح سازگاری
توجه داشته باشید که فرض MVS در مورد سازگاری با عقب، امکان پذیر است زیرا به سادگی نسخه های ناسازگار یک ماژول را به عنوان یک ماژول جداگانه در نظر می گیرد. از نظر SemVer، این بدان معناست که A 1.x و A 2.x ماژولهای مجزا در نظر گرفته میشوند و میتوانند در گراف وابستگی حلشده همزیستی داشته باشند. این به نوبه خود با این واقعیت امکان پذیر است که نسخه اصلی در مسیر بسته در Go کدگذاری شده است، بنابراین هیچ تضاد زمان کامپایل یا زمان پیوند وجود ندارد.
در بازل چنین تضمینی نداریم. بنابراین ما به راهی برای نشان دادن شماره "نسخه اصلی" نیاز داریم تا نسخه های ناسازگار با عقب را شناسایی کنیم. این عدد، سطح سازگاری نامیده میشود و توسط هر نسخه ماژول در دستورالعمل module()
مشخص میشود. با در دست داشتن این اطلاعات، زمانی که تشخیص میدهیم نسخههایی از همان ماژول با سطوح سازگاری متفاوت در نمودار وابستگی حلشده وجود دارد، میتوانیم خطا ایجاد کنیم.
نام مخازن
در بازل، هر وابستگی خارجی یک نام مخزن دارد. گاهی اوقات، ممکن است از یک وابستگی یکسان از طریق نامهای مخزن مختلف استفاده شود (به عنوان مثال، هر دو @io_bazel_skylib
و @bazel_skylib
به معنای Bazel skylib هستند )، یا ممکن است از یک نام مخزن برای وابستگیهای مختلف در پروژههای مختلف استفاده شود.
در Bzlmod، مخازن را می توان توسط ماژول های Bazel و افزونه های ماژول تولید کرد. برای حل تداخل نام مخزن، ما در سیستم جدید از مکانیزم نگاشت مخزن استفاده می کنیم. در اینجا دو مفهوم مهم وجود دارد:
نام مخزن متعارف: نام مخزن منحصربفرد جهانی برای هر مخزن. این نام دایرکتوری است که مخزن در آن زندگی می کند.
به صورت زیر ساخته شده است ( هشدار : قالب نام متعارف یک API نیست که باید به آن وابسته باشید، در هر زمان ممکن است تغییر کند):- برای مخازن ماژول 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
)
- برای مخازن ماژول Bazel:
نام مخزن محلی : نام مخزن مورد استفاده در فایل های
BUILD
و.bzl
. در یک مخزن. وابستگی یکسان میتواند نامهای محلی متفاوتی برای مخازن مختلف داشته باشد.
به شرح زیر تعیین می شود:
هر مخزن دارای یک فرهنگ لغت نگاشت مخزن از وابستگی های مستقیم خود است که نقشه ای از نام مخزن محلی تا نام مخزن متعارف است. ما از نگاشت مخزن برای حل نام مخزن هنگام ساخت یک برچسب استفاده می کنیم. توجه داشته باشید که هیچ تداخلی در نامهای مخزن متعارف وجود ندارد و استفاده از نامهای مخزن محلی را میتوان با تجزیه فایل MODULE.bazel
کشف کرد، بنابراین تداخلها را میتوان به راحتی پیدا کرد و بدون تأثیر بر وابستگیهای دیگر برطرف کرد.
عمق های سخت
قالب جدید مشخصات وابستگی به ما امکان می دهد بررسی های دقیق تری انجام دهیم. بهویژه، اکنون ما اصرار میکنیم که یک ماژول فقط میتواند از مخازن ایجاد شده از وابستگیهای مستقیم خود استفاده کند. این به جلوگیری از شکستگیهای تصادفی و سختزدایی در هنگام تغییر چیزی در نمودار وابستگی گذرا کمک میکند.
Strict Deps بر اساس نگاشت مخزن پیاده سازی شده است. اساساً نقشهبرداری مخزن برای هر مخزن شامل تمام وابستگیهای مستقیم آن است، هیچ مخزن دیگری قابل مشاهده نیست. وابستگی های قابل مشاهده برای هر مخزن به شرح زیر تعیین می شود:
- مخزن ماژول Bazel می تواند تمام مخازن معرفی شده در فایل
MODULE.bazel
را از طریقbazel_dep
وuse_repo
. - یک مخزن افزونه ماژول میتواند تمام وابستگیهای قابل مشاهده ماژولی را که افزونه را ارائه میکند، بهعلاوه همه مخازن دیگر تولید شده توسط همان افزونه ماژول را ببیند.
دفاتر ثبت
بزلمود با درخواست اطلاعات آنها از رجیستری های بازل، وابستگی ها را کشف می کند. رجیستری Bazel به سادگی پایگاه داده ای از ماژول های Bazel است. تنها فرمی که از رجیستری ها پشتیبانی می شود، یک فهرست فهرست است که یک فهرست محلی یا یک سرور HTTP ایستا با فرمت خاصی است. در آینده، ما قصد داریم برای ثبتهای تک ماژول پشتیبانی اضافه کنیم، که صرفاً مخازن git حاوی منبع و تاریخچه یک پروژه هستند.
ثبت فهرست
رجیستری فهرست یک دایرکتوری محلی یا یک سرور HTTP ثابت است که حاوی اطلاعات فهرستی از ماژولها، از جمله صفحه اصلی، نگهدارندهها، فایل MODULE.bazel
هر نسخه و نحوه واکشی منبع هر نسخه است. قابل ذکر است که نیازی به ارائه خود آرشیو منبع ندارد.
یک رجیستری فهرست باید از فرمت زیر پیروی کند:
-
/bazel_registry.json
: یک فایل JSON حاوی ابرداده برای رجیستری. در حال حاضر، فقط یک کلید دارد،mirrors
، که فهرستی از آینهها را برای بایگانی منبع مشخص میکند. -
/modules
: دایرکتوری حاوی یک زیر شاخه برای هر ماژول در این رجیستری. -
/modules/$MODULE
: دایرکتوری حاوی یک زیر شاخه برای هر نسخه از این ماژول و همچنین فایل زیر:-
metadata.json
: یک فایل JSON حاوی اطلاعات مربوط به ماژول، با فیلدهای زیر:-
homepage
: آدرس صفحه اصلی پروژه. -
maintainers
ها: لیستی از اشیاء JSON که هر کدام مربوط به اطلاعات نگهدارنده ماژول در رجیستری است. توجه داشته باشید که این لزوماً مشابه نویسندگان پروژه نیست. -
versions
ها: فهرستی از تمام نسخه های این ماژول که در این رجیستری یافت می شوند. -
yanked_versions
: لیستی از نسخه های یانک شده این ماژول. این در حال حاضر بدون عملیات است، اما در آینده، نسخههای یانک شده نادیده گرفته میشوند یا با خطا مواجه میشوند.
-
-
-
/modules/$MODULE/$VERSION
: دایرکتوری حاوی فایلهای زیر:-
MODULE.bazel
: فایلMODULE.bazel
این نسخه ماژول. -
source.json
: یک فایل JSON حاوی اطلاعاتی در مورد نحوه واکشی منبع این نسخه ماژول، با فیلدهای زیر:-
url
: آدرس آرشیو منبع. -
integrity
: جمع کنترلی یکپارچگی زیرمنابع آرشیو. -
strip_prefix
: پیشوند فهرستی که هنگام استخراج بایگانی منبع باید حذف شود. -
patches
: لیستی از رشته ها که هر کدام یک فایل وصله را برای اعمال در بایگانی استخراج شده نام می برند. فایل های وصله در زیر پوشه/modules/$MODULE/$VERSION/patches
قرار دارند. -
patch_strip
: همانند آرگومان--strip
پچ یونیکس.
-
-
patches/
: یک دایرکتوری اختیاری حاوی فایل های پچ.
-
ثبت مرکزی بازل
Bazel Central Registry (BCR) یک فهرست فهرست واقع در registry.bazel.build است. محتویات آن توسط مخزن GitHub bazelbuild/bazel-central-registry
شود.
BCR توسط جامعه بازل نگهداری می شود. از مشارکت کنندگان برای ارسال درخواست های کشش استقبال می شود. به سیاست ها و رویه های ثبت مرکزی بازل مراجعه کنید.
علاوه بر پیروی از فرمت یک رجیستری فهرست معمولی، BCR به یک فایل presubmit.yml
برای هر نسخه ماژول نیاز دارد ( /modules/$MODULE/$VERSION/presubmit.yml
). این فایل چند هدف اساسی ساخت و آزمایش را مشخص میکند که میتوان از آنها برای بررسی صحت این نسخه ماژول استفاده کرد و توسط خطوط لوله CI BCR برای اطمینان از قابلیت همکاری بین ماژولها در BCR استفاده میشود.
انتخاب رجیستری ها
رجیستری با پرچم قابل تکرار --registry
را می توان برای تعیین لیست رجیستری ها برای درخواست ماژول ها استفاده کرد، بنابراین می توانید پروژه خود را برای واکشی وابستگی ها از یک رجیستری شخص ثالث یا داخلی تنظیم کنید. ثبت های قبلی اولویت دارند. برای راحتی کار، می توانید لیستی از پرچم های --registry
را در فایل .bazelrc
. پروژه خود قرار دهید.
برنامه های افزودنی ماژول
افزونههای ماژول به شما امکان میدهند سیستم ماژول را با خواندن دادههای ورودی از ماژولها در سراسر نمودار وابستگی، انجام منطق لازم برای حل وابستگیها و در نهایت ایجاد مخازن با فراخوانی قوانین مخزن گسترش دهید. آنها از نظر عملکرد مشابه ماکروهای WORKSPACE
امروزی هستند، اما بیشتر در دنیای ماژول ها و وابستگی های انتقالی مناسب هستند.
پسوندهای ماژول در فایلهای .bzl
. تعریف میشوند، درست مانند قوانین repo یا ماکروهای 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" و برخی برای "محموله" مشخص شده اند. وقتی این نمودار وابستگی نهایی شد (برای مثال، شاید B 1.2
واقعاً یک bazel_dep
در D 1.3
داشته باشد اما به دلیل C
به D 1.4
ارتقا یافته است)، پسوندهای "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
برای اعلام آنها استفاده کنید. این برای ارضای شرایط سختگیرانه deps و جلوگیری از تضاد نام مخزن محلی است.
use_repo(
maven,
"org_junit_junit",
guava="com_google_guava_guava",
)
مخازن تولید شده توسط یک افزونه بخشی از API آن هستند، بنابراین از برچسبهایی که مشخص کردهاید، باید بدانید که افزونه "maven" قرار است مخزنی به نام "org_junit_junit" و یکی به نام "com_google_guava_guava" تولید کند. با use_repo
، می توانید به صورت اختیاری آنها را در محدوده ماژول خود تغییر نام دهید، مانند "گوآوا" در اینجا.
تعریف پسوند
پسوندهای ماژول با استفاده از تابع module_extension
مشابه قوانین repo تعریف می شوند. هر دو دارای عملکرد پیاده سازی هستند. اما در حالی که قوانین repo دارای تعدادی ویژگی هستند، پسوندهای ماژول دارای تعدادی tag_class
es هستند که هر کدام دارای تعدادی ویژگی است. کلاسهای برچسب، طرحوارههایی را برای برچسبهای استفاده شده توسط این پسوند تعریف میکنند. ادامه مثال ما از پسوند فرضی "maven" در بالا:
# @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 را برای تولید مخازن فراخوانی کند:
# @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 maven.*
روی ماژول را نشان میدهد. سپس از ابزار CLI Coursier برای تماس با Maven و انجام وضوح فراخوانی می کنیم. در نهایت، با استفاده از قانون فرضی maven_single_jar
repo، از نتیجه وضوح برای ایجاد تعدادی مخزن استفاده می کنیم.
لینک های خارجی
- تعمیرات اساسی وابستگی های خارجی Bazel (سند طراحی اصلی Bzlmod)
- خط مشی ها و رویه های ثبت مرکزی بازل
- مخزن مرکزی Bazel Registry GitHub
- گفتگوی BazelCon 2021 در Bzlmod