رام کردن تضادهای وابستگی Bazel: مطالعه موردی از اندروید

برای بسیاری از توسعه دهندگان، “تضاد وابستگی” عبارتی است که بلافاصله باعث سردرد می شود. این امر به ویژه در هنگام حرکت از یک اکوسیستم آشنا مانند Gradle به ابزاری قدرتمند اما پیچیده مانند Bazel صادق است.
چند وقت پیش در مورد تجربه خود در زمینه ساخت یک برنامه اندروید با Bazel نوشتم (می توانید در مورد آن در اینجا بخوانید: ساخت یک برنامه پخش موسیقی مدرن اندروید با Bazel…).
من اخیراً تصمیم گرفتم برخی از کتابخانه ها را در این پروژه به روز کنم، عمدتاً با انتقال به Compose BOM (androidx.compose:compose-bom:2024.12.01).
🧨 مشکل شروع می شود: یک به روز رسانی ساده کتابخانه
من فایل MODULE.bazel خود را با وابستگی های جدید به روز کردم. تلاش اولیه به این صورت بود:
https://medium.com/media/ab6ac611fb4ff73c1120f2dba1a8df3e/href
من بیلد را اجرا کردم و … شکست خورد. با یک گزارش خطای مرموز و طولانی که به تعارض وابستگی کلاسیک تبدیل شد، استقبال شدم.
در اینجا فقط یک قطعه کوچک از نمودار وابستگی است:

در اصل، مشکل آشنا بود. به عنوان مثال، نگاه کردن به گزارش:
- androidx.activity:activity-compose:1.7.0 (که من استفاده کردم) صراحتاً androidx.compose.ui:ui:1.0.1 را درخواست می کند.
- اما Compose BOM جدید (2024.12.01) در حال کشیدن androidx.compose.ui:ui:1.7.6 (نسخه بسیار جدیدتر) بود.
این مرا دیوانه کرد. در زمانی که با Gradle کار می کردم، به ندرت با چنین درگیری های شدید مواجه می شدم. Gradle معمولاً به اندازه کافی “باهوش” به نظر می رسد که بتواند آخرین نسخه را انتخاب کند و ادامه دهد.
🧠 درک “چرا”: گریدل در مقابل بازل
پس از کمی حفاری، متوجه شدم که تفاوت اساسی در نحوه مدیریت نسخه ها توسط این ابزارهای ساخت است.
1. Gradle: “آخرین نسخه برنده می شود” (پیش فرض)
به طور پیش فرض، Gradle از استراتژی “انتخاب آخرین نسخه” استفاده می کند. اگر با درگیری مواجه شد، به سادگی انتخاب می کند آخرین نسخه وابستگی موجود در نمودار.
- A → C نسخه 1.0
- B → C نسخه 2.0
- خروجی گرید: C نسخه 2.0 (به طور خودکار انتخاب می شود)
2. Bazel (Bzlmod): “انتخاب حداقل نسخه” (MVS)
بازل با معرفی Bzlmod از الگوریتم Minimum Version Selection (MVS) استفاده می کند که توسط ماژول های Go نیز استفاده می شود (مستندات رسمی Bzlmod را می توانید در اینجا بخوانید).
اصل MVS: بازل انتخاب می کند حداقل نسخه ای که راضی می کند همه محدودیت ها در نمودار وابستگی این الگوریتم بسیار قطعی و قابل تکرار است.
بیایید به مثال کلاسیک از مستندات Bazel نگاه کنیم:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
در این صورت بازل D 1.1.why چون هست را انتخاب می کند بالاترین نسخه به صراحت مورد نیاز است در هر ماژول (C 1.1 به این نیاز دارد). یعنی "حداقل" زیرا D 1.1 اولین نسخه ای است که تمام الزامات را برآورده می کند. حتی اگر D 1.2 وجود داشت، MVS وجود داشت نه آن را انتخاب کنید مگر اینکه ماژول دیگری به طور خاص آن را درخواست کرده باشد.
مقایسه سریع
| Aspect | Gradle (Default) | Bazel (MVS) |
|----------------------------|--------------------------|--------------------------------------------|
| Algorithm | Newest Version Selection | Minimal Version Selection |
| Version Choice | Newest version found | Minimum version that meets all constraints |
| Example (D 1.0 vs 1.1). | D 1.1 selected | D 1.1 selected |
| If D 1.2 is available | D 1.2 selected | D 1.1 is still selected |
| Reproducibility | Medium | Very High |
درگیری من به این دلیل بود که MVS نتوانست a را پیدا کند مجرد نسخه ای که با تمام محدوده های درخواستی نسخه های مختلف مطابقت دارد.
🧩 راه حل: تضادها را بیابید و برطرف کنید
پس چگونه این را برطرف کنیم؟
بسیاری از توصیه های آنلاین (حتی از اربابان جدید هوش مصنوعی ما مانند ChatGPT، Claude، Gemini و غیره) به شما می گوید که فقط version_conflict_policy = اضافه کنید. "چفت شد" به maven خود را نصب کنید.
https://medium.com/media/7fdc2761fcc350867ff26bbbcfdd9b50/href
با این حال این کافی نیست این سیاست فقط به بازل میگوید که نسخهها را «رفع» کند با موفقیت اجازه می دهد؛ در واقع اینطور نیست من تصمیم میگیرم خود درگیری شما همچنان همان خطا را دریافت خواهید کرد.
پس از جستجو در مخزن rules_jvm_external، راه حل واقعی را پیدا کردم: استفاده از maven.artifact برای دستی "قدرت" یا "لغو" نسخه یک کتابخانه خاص
اما… از کجا بدانیم که نسخه هایی را انتخاب کنید که گزارش خطا تا این حد نویز باشد؟
مرحله 1: اشکال زدایی و پین وابستگی ها
کلید اجرای این دستور (از rules_jvm_external) برای انجام فرآیند تعمیر و dump است پر دفتر خاطرات:
bazel clean --expunge
bazel run @maven//:pin &> maven_pin.txt
این دستور فرآیند تفکیک وابستگی را شروع کرده و ذخیره می کند همه خروجی (از جمله خطاهای پرمخاطب) به فایلی به نام maven_pin.txt.
مرحله 2: گزارش را تجزیه و تحلیل کنید
اکنون maven_pin.txt را باز کنید. از پایین به بالا بروید (یا جستجو کنید "اعتیادهای متعارض") تا زمانی که ریشه مشکل را پیدا کنید. در مورد من، فایل log به این شکل بود:
...
Resolution error: Conflicting dependencies:
androidx.collection:collection:1.0.0 or 1.1.0 or 1.2.0 or 1.4.0 or 1.4.2 or [1.2.0] wanted by
androidx.activity:activity:1.7.0 wants 1.0.0
├─ androidx.activity:activity-ktx:1.7.0 wants androidx.activity:activity:[1.7.0]
...
مقصر مشخص بود: androidx.collection:collection. بسیاری از وابستگیهای مختلف به نسخههای مختلفی نیاز داشتند.
مرحله 3: یک نسخه تعریف کنید و جایگزین را اعمال کنید
اکنون که کتابخانه متضاد را میدانیم، میتوانیم یک نسخه را برای کنترل همه آنها انتخاب کنیم.
- استراتژی: سعی کن انتخاب کنی آخرین نسخه فهرست شده در گزارش تضاد (در این مورد 1.4.2). فرض بر این است که این نسخه با نسخه قبلی سازگار است و همه ماژول ها را برآورده می کند.
- اگر آخرین نسخه همچنان باعث خطا می شود، آخرین نسخه بعدی را امتحان کنید و دوباره امتحان کنید.
این لغو را به فایل MODULE.bazel خود اضافه کنید، قبل از بلوک maven.install:
https://medium.com/media/f8acb092725a9b03da50a68e93bc2bbe/href
مرحله 4: تکرار و تکرار
دستور bazel run @maven//:pin &>maven_pin.txt را دوباره اجرا کنید. فایل maven_pin.txt را بررسی کنید. اگر تعارض دیگری برای الف رخ دهد متفاوت است کتابخانه، مراحل 2 و 3 را برای آن کتابخانه تکرار کنید.
برای پروژهام، مجبور شدم چندین کتابخانه را جایگزین کنم:
https://medium.com/media/228e70c19e2f006f69e25dea8dce9f19/href
پس از افزودن همه جایگزینهای لازم، دستور bazel run @maven//:pin باید با موفقیت و بدون خطا اجرا شود.
✅ نتیجه نهایی
اکنون می توانم طبق معمول دستورات را برای ساخت پروژه خود اجرا کنم:
bazel build //app/src/main:app
adb install -r bazel-bin/app/src/main/app.apk
adb shell am start -n com.amary.my.music/.MainActivity
و… انجام شد!
این برنامه ساخته شده است و با تمام وابستگی های به روز شده کاملاً کار می کند 🎉

📦 کد منبع در اینجا موجود است:
👉 github.com/amary21/my-music-bazel
💡نظرات پایانی
رزولوشن وابستگی MVS Bazel در ابتدا می تواند «نویزدار» و سفت و سخت به نظر برسد، به خصوص در مقایسه با رویکرد «این فقط کار می کند» Gradle. با این حال، ماهیت قطعی و قابل تکرار آن برای ثبات ساخت طولانی مدت بسیار ارزشمند است. راه حل این است که صریح باشید و زمانی که MVS نمی تواند راهی پیدا کند، بازگردانی دستی ارائه دهید.
یکی از چیزهایی که من هنوز به آن فکر می کنم این است که بهترین راه برای مدیریت به روز رسانی های Bazel است قوانین خودش (مانند rules_android، rules_kotlin). بهروزرسانی آنها اغلب منجر به تغییرات قابل توجهی در پیکربندی میشود که گاهی اوقات حتی نیاز به تغییر نسخه Bazel دارد. به همین دلیل است که من هنوز از نسخه 2024 Compose BOM در این پروژه استفاده می کنم. نسخه 2025 حداقل به Kotlin 2 نیاز دارد و کامپایلر آن به JetBrains منتقل شده است، که به یک به روز رسانی دشوار برای rules_kotlin نیاز دارد.
اگر تجربه یا نکتهای در مورد مدیریت بهروزرسانیهای خطمشی Bazel دارید، مایلم آنها را در نظرات زیر بشنوم!
📫 بیایید وصل شویم
- 📧 ایمیل: taufik.amary@gmail.com
- 💻 GitHub: github.com/amary21
- 💼 لینکدین: www.linkedin.com/in/t-amar
رام کردن درگیریهای وابستگی Bazel: یک مطالعه موردی اندروید در ابتدا در ProAndroidDev در Medium پست شد، که در آن افراد با برجسته کردن و پاسخ دادن به این داستان به گفتگو ادامه میدهند.



