Azimjon Abdurasulov

Bazel 7: Tashqi dependency'lar — Bzlmod, BCR, SPM paketlari va xcframework'lar

📦 Bazel iOS — Mukammal Qo‘llanma & Amaliyot

Bazel 7: Tashqi dependency'lar — Bzlmod, BCR, SPM paketlari va xcframework'lar

Holat: 2026-yil oktabr. Bu darsda Bazel'ga tashqi kodni uch yo'l bilan kiritamiz: BCR registry orqali Bazel modullar (bazel_dep), rules_swift_package_manager orqali Swift Package Manager paketlari va apple_*_xcframework_import orqali tayyor binary'lar. Kod misollari Bazel 9.3.0 (Bazelisk 1.29.0), rules_apple 5.3.0, rules_swift 4.2.1, apple_support 2.10.1, rules_swift_package_manager 1.25.0 va Xcode 27.1 bilan Apple M4 Pro'da tekshirilgan.

Bu darsdan keyin siz: loyihangizga istalgan tashqi kutubxonani — Bazel moduli, SPM paketi yoki yopiq binary — to'g'ri versiya bilan, qaytariladigan (reproducible) va xavfsiz tarzda ulay olasiz.

  • Bzlmod'ning ishlash modelini tushunasiz: module, repo, registry (BCR) va Minimal Version Selection (MVS).
  • MODULE.bazel'da bazel_dep bilan dependency qo'shasiz, bazel mod bilan grafni o'rganasiz.
  • MODULE.bazel.lock nega muhimligini va override'lar (single_version_override, local_path_override, git_override, archive_override) qachon kerakligini bilasiz.
  • rules_swift_package_manager bilan haqiqiy SPM paketini (swift-collections) iOS ilovaga ulaysiz — to'liq, tekshirilgan oqim bilan.
  • Tayyor .xcframework'ni apple_static_xcframework_import bilan import qilasiz va http_archive + sha256 nega supply chain xavfsizligi ekanini tushunasiz.
  • bazel vendor bilan dependency'larni offline build uchun lokal nusxalaysiz.

Mundarija

  1. Nega tashqi dependency — va u qaysi muammoni tug'diradi
  2. Bzlmod: modullar, repolar va registry
  3. [Birinchi dependency: MODULE.bazel va bazel_dep](#birinchi)
  4. MVS — versiyalar qanday tanlanadi
  5. [bazel mod bilan grafni o'rganish](#mod)
  6. [MODULE.bazel.lock va reproduksiya](#lock)
  7. Override'lar — dependency'ni o'z qo'lingizga olish
  8. [SPM paketlarini ulash: rules_swift_package_manager](#spm)
  9. [Ko'prik ichida nima bor: module extension va use_repo](#extension)
  10. Tayyor binary'lar: xcframework import
  11. [http_archive, integrity va supply chain xavfsizligi](#supply)
  12. [Offline build: bazel vendor](#vendor)
  13. Qaysi yo'lni qachon tanlash
  14. Amaliy mashqlar
  15. O'zingizni tekshiring
  16. Xulosa
  17. Manbalar

1. Nega tashqi dependency — va u qaysi muammoni tug'diradi {#nega}

5-darsda birinchi iOS ilovamizni, 6-darsda esa unga testlar yozdik. Butun kod bizniki edi: //Core, //App. Lekin haqiqiy loyihada siz hamma narsani noldan yozmaysiz. Tarmoq (networking), Auto Layout, ma'lumotlar bazasi, analitika, log yig'ish — bularning aksariyati allaqachon yozilgan va ochiq kodli yoki tayyor kutubxona sifatida mavjud. Masala shunda: bu kodni loyihangizga qanday olib kirasiz — va buni shunday qilasizki, hamkasbingizdagi va CI'dagi build aynan siznikidek bo'lsin.

Xcode'da bu masala odatda ikki yo'l bilan hal qilinadi: Swift Package Manager (SPM) paketlarini "File → Add Package Dependencies" orqali qo'shish, yoki CocoaPods. Ikkalasi ham ishlaydi, lekin ikkala holatda ham bir muammo bor: versiya va yuklab olingan fayl aniq qulflanmagan bo'lsa, "menda ishlaydi, sende ishlamaydi" holatiga tushasiz. SPM Package.resolved fayli yordam beradi, lekin swiftc qaysi fayllarni, qaysi flaglar bilan kompilyatsiya qilganini Xcode build tizimi sizga ochiq ko'rsatmaydi.

Bazel bu masalaga boshqacha yondashadi. Bazel uchun tashqi dependency ham xuddi sizning kodingiz kabi — e'lon qilingan input'lardan hosil bo'ladigan sof funksiya. Har bir tashqi paket:

  • aniq versiya bilan e'lon qilinadi (rules_swift emas, rules_swift 4.2.1);
  • yuklab olingan arxiv sha256 (integrity) bilan tekshiriladi — ya'ni bayt-baytigacha bir xil fayl;
  • natija MODULE.bazel.lock faylida qulflanadi — hamkasbingizda ham, CI'da ham aynan shu graf qayta tiklanadi.

Bu "{Fast, Correct} — ikkitasini tanla" shiorining dependency'larga tatbig'i: sekinroq birinchi marta, lekin har doim to'g'ri va qaytariladigan.

🚀 Motivatsiya: Bazel'da dependency qo'shish — bu "qaysidir versiya yuklanib qoldi" degan lotereya emas. Siz aniq versiyani yozasiz, Bazel uni sha256 bilan tekshiradi va MODULE.bazel.lock'ga yozadi. Olti oydan keyin ham, boshqa Mac'da ham, CI'da ham — aynan o'sha fayllar, aynan o'sha build. Bu "clean build fixes it" davrining oxiri.

Bu darsda biz tashqi kodni kiritishning uchta yo'lini o'rganamiz, va ularning har birini o'z qo'limiz bilan ishga tushiramiz:

Tashqi kodni kiritishning uchta yo'li: BCR registry orqali Bazel moduli, rules_swift_package_manager orqali SPM paketi va xcframework import orqali tayyor binary — uchalasi ham sha256 bilan hermetik

Yuqoridagi uch ustun — darsning xaritasi. Bazel moduli (1-ustun) Bazel'ni biladigan ruleset va kutubxonalar uchun; SPM paketi (2-ustun) katta iOS ekotizimi uchun; tayyor binary (3-ustun) esa yopiq kodli vendor SDK'lari uchun. Keling, poydevordan — Bzlmod'dan — boshlaymiz.


2. Bzlmod: modullar, repolar va registry {#bzlmod}

Avval terminologiya, chunki keyingi hamma narsa shu uch so'zga tayanadi. Bazel'ning tashqi dependency tizimi — Bzlmod (codename) — ikki tushunchaga quriladi (Bazel rasmiy hujjati, external/overview):

  • Module (modul) — versiyalanadigan Bazel loyihasi. Har modulning ildizida MODULE.bazel fayli bo'ladi, unda modul nomi, versiyasi va to'g'ridan-to'g'ri bog'liqliklari yoziladi. rules_swift — modul. rules_apple — modul. Sizning loyihangiz ham — root module.
  • Repository (repo) — ildizida chegara belgisi (MODULE.bazel, REPO.bazel, yoki eski WORKSPACE) bo'lgan papka daraxti. Har bir modul lokal diskda bitta repo bilan ifodalanadi.

Bazel ildiz modulingizdan boshlaydi, uning MODULE.bazel faylini o'qiydi va har bir bog'liqlikning MODULE.bazel faylini registry'dan so'raydi — bu esa butun transitive grafni topguncha davom etadi.

Registry — Bazel modullari haqidagi ma'lumotlar bazasi. Standart registry — Bazel Central Registry (BCR), bcr.bazel.build manzilida (web interfeysi: registry.bazel.build). Registry har bir modulning har bir versiyasi uchun ikki asosiy faylni saqlaydi (external/registry):

  • MODULE.bazel — o'sha versiyaning manifesti (bog'liqliklari);
  • source.json — manbani qayerdan olish (URL, integrity sha256, strip_prefix).

Diqqat qiling: registry arxivning o'zini saqlamaydi — u faqat "qayerdan yuklab olish va natija qanday sha256 bo'lishi kerak"ligini biladi. Arxivning o'zi GitHub Releases'dan yoki boshqa manbadan yuklanadi.

🧠 Esda tuting: MODULE.bazel'da siz faqat to'g'ridan-to'g'ri bog'liqliklarni yozasiz. Transitive bog'liqliklar (sizning bog'liqligingizning bog'liqligi) avtomatik hal qilinadi. Bu — eski WORKSPACE tizimining eng katta dardiga davo: WORKSPACE'da siz hamma transitive dependency'ni qo'lda, to'g'ri tartibda yozishga majbur edingiz.

WORKSPACE o'ldi — uzil-kesil

Agar internetda Bazel bo'yicha eski qo'llanmani ko'rsangiz va unda WORKSPACE fayli, http_archive(...) to'g'ridan-to'g'ri WORKSPACE'da, yoki load("@build_bazel_rules_apple//...") ko'rsangiz — u eskirgan. Bazel 9.0'da (2026-yil yanvar) WORKSPACE kodi butunlay olib tashlangan: --enable_bzlmod va --enable_workspace endi hech narsa qilmaydigan (no-op) flaglar (Bazel 9.0 release notes, bazelbuild/bazel#26131). Bazel 8.0'da WORKSPACE allaqachon default'da o'chirilgan edi; 9.0 uni o'chirib yubordi.

Buning amaliy natijasi: **biz faqat MODULE.bazel bilan ishlaymiz.** Va rules_apple 5.x, rules_swift 4.x snippetlarida repo_name = "build_bazel_rules_apple" kabi qatorlar yo'q — biz to'g'ridan-to'g'ri @rules_apple// va @rules_swift// yozamiz (bu FACTS'da tekshirilgan va eng sodda yo'l).

⚠️ Diqqat: Bazel 9'da yana bir o'zgarish — C/C++ oilasi rule'lari (shu jumladan objc_library) endi Bazel yadrosida yo'q, ularni @rules_cc'dan load() qilish kerak. Bu bizga kamdan-kam kerak bo'ladi (biz asosan Swift yozamiz), lekin eski Objective-C misollarida objc_library to'g'ridan-to'g'ri ishlatilgan bo'lsa — endi load("@rules_cc//cc:defs.bzl", "objc_library") kerak.


3. Birinchi dependency: MODULE.bazel va bazel_dep {#birinchi}

Keling, bo'sh ish papkasida kichik workspace yasaymiz va birinchi dependency'ni qo'shamiz. (Barcha misollarni men scratchpad/work/bazel/07_dependency/ papkasida haqiqatan ishga tushirdim — natijalar quyida.)

Avval minimal MODULE.bazel:

# MODULE.bazel
module(name = "bzlmod_demo", version = "1.0.0")

bazel_dep(name = "apple_support", version = "2.10.1")
bazel_dep(name = "rules_swift", version = "4.2.1")
bazel_dep(name = "rules_apple", version = "5.3.0")
bazel_dep(name = "bazel_skylib", version = "1.9.2")

Bu yerda har bir bazel_dep qatori — bitta to'g'ridan-to'g'ri bog'liqlik. Nimalarni qo'shdik:

  • **apple_support** — Apple toolchain'ini (Xcode, clang) topadi va sozlaydi. rules_apple README unga ko'ra apple_support rules_cc'dan yuqorida yozilishi kerak, chunki birinchi ro'yxatdan o'tgan toolchain g'olib bo'ladi.
  • **rules_swift** — Swift kompilyatsiyasi (swift_library, swift_binary, swift_test).
  • **rules_apple** — Apple bundling (ios_application, ios_unit_test, xcframework rule'lari).
  • **bazel_skylib** — Starlark uchun standart yordamchi kutubxona (bu yerda MVS'ni ko'rsatish uchun qo'shdik).

Endi bitta swift_library qo'shamiz:

# Core/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")

swift_library(
    name = "Core",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "Core",
    visibility = ["//visibility:public"],
)
// Core/Sources/Greeter.swift
public struct Greeter {
    public init() {}
    public func salom(_ ism: String) -> String {
        "Salom, \(ism)!"
    }
}

Va build qilamiz:

bazel build //Core:Core

Natija (M4 Pro, disk cache bilan):

INFO: Analyzed target //Core:Core (167 packages loaded, 4479 targets configured).
Target //Core:Core up-to-date:
  bazel-bin/Core/Core.swiftmodule
  bazel-bin/Core/libCore.a
INFO: Elapsed time: 19.782s, Critical Path: 0.87s
INFO: 288 processes: 173 disk cache hit, 113 internal, 1 darwin-sandbox, 1 worker.

Bitta kichik swift_library uchun 167 package yuklandi va 4479 target konfiguratsiya qilindi — chunki rules_swift, rules_apple, apple_support o'zlari ham bir talay transitive bog'liqlikni (protobuf, abseil, rules_cc, rules_python...) olib keladi. Bu — Bazel'ning tabiati: u butun grafni oldindan biladi. Yaxshi xabar: bularning hammasi MODULE.bazel.lock'ga yoziladi va disk cache'da saqlanadi, shuning uchun ikkinchi marta build soniyalarda tugaydi.

💡 Pro tip: bazel_dep'da version — bu minimal so'rov, aniq qulf emas (qulflash single_version_override yoki lock fayl ishi). Agar transitive bog'liqliklardan biri bazel_skylib'ning yuqoriroq versiyasini so'rasa, MVS yuqorirog'ini tanlaydi. Buni keyingi bo'limda aniq ko'ramiz.

compatibility_level — mos kelmaslikni e'lon qilish

Semantik versiya (SemVer)'da katta (major) raqamning oshishi "buzuvchi o'zgarish" degani. Lekin MVS har doim eng yuqori versiyani tanlaydi va "yangi versiya orqaga mos" deb faraz qiladi. Agar modul muallifi chindan ham buzuvchi o'zgarish kiritsa-chi?

Buning uchun module() funksiyasida compatibility_level bor (masalan, rules_swiftning MODULE.bazel'ida compatibility_level = 3). Bu raqam oshsa, Bazel o'sha modulning turli compatibility_level'dagi versiyalarini bir grafda birga yashay olmaydi deb biladi va aniq xato beradi, "jim-jit noto'g'ri build" o'rniga. Ya'ni compatibility_level — bu MVS'ning "orqaga mos" farazini buzadigan holatni ochiq e'lon qilish usuli.


4. MVS — versiyalar qanday tanlanadi {#mvs}

Dependency boshqaruvidagi klassik muammo — "diamond" (olmos) muammosi. Tasavvur qiling:

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

B D 1.0'ni, C esa D 1.1'ni so'raydi. Qaysi D ishlatilsin? npm tarixan bir nechta nusxani yonma-yon saqlardi (node_modules ichida takror-takror), bu esa hajmni va chalkashlikni oshirardi. Bazel esa Minimal Version Selection (MVS) algoritmidan foydalanadi — bu Go modul tizimidan olingan (Russ Cox, research.swtch.com/vgo-mvs).

MVS qoidasi oddiy: har modul uchun, uni so'ragan barcha versiyalardan eng yuqorisi tanlanadi. Yuqoridagi misolda D 1.1 tanlanadi. "Minimal" so'zi shuni bildiradi: D 1.1 — barcha talablarni qondiradigan eng erta versiya; agar D 1.2 mavjud bo'lsa ham, uni tanlamaymiz, chunki hech kim uni so'ramagan. Natija — yuqori aniqlikdagi (high-fidelity) va qaytariladigan (reproducible) tanlov.

Buni o'z grafimizda ko'raylik. bazel mod explain bazel_skylib — bazel_skylib grafda qayerda paydo bo'lishini va kim uni so'rashini ko'rsatadi:

<root> (bzlmod_demo@1.0.0)
├───apple_support@2.10.1
│   ├───bazel_features@1.51.0
│   │   └───bazel_skylib@1.9.2 #
│   ├───bazel_skylib@1.9.2 #
│   └───rules_cc@0.2.26
│       └───bazel_skylib@1.9.2 #
├───bazel_skylib@1.9.2 #
├───rules_apple@5.3.0
│   ├───bazel_skylib@1.9.2 #
│   ├───rules_python@1.7.0
│   │   └───stardoc@0.7.2
│   │       └───bazel_skylib@1.9.2 #
│   └───rules_shell@0.6.1
│       └───bazel_skylib@1.9.2 #
└───rules_swift@4.2.1
    ├───abseil-cpp@20250814.1
    │   └───bazel_skylib@1.9.2 #
    └───protobuf@34.0.bcr.1
        ├───rules_java@9.1.0
        │   └───bazel_skylib@1.9.2 #
        └───rules_kotlin@1.9.6
            └───bazel_skylib@1.9.2 #

(Natija qisqartirildi — haqiqatda o'nlab modul bazel_skylib'ni so'raydi.) # belgisi — tanlangan versiya. Diqqat qiling: o'nlab turli modul bazel_skylib'ni so'rasa ham, grafda faqat bitta versiya — 1.9.2 — yashaydi. Hech qanday takror nusxa yo'q.

Endi eng qiziqi: nega aynan 1.9.2? --verbose --include_unused bilan Bazel qaysi versiya qaysisiga almashtirilganini aytadi:

│   ├───bazel_features@1.27.0 (to 1.51.0, cause rules_swift@4.2.1, rules_swift@4.1.1)
...
│           ├───rules_proto@4.0.0 (to 7.1.0, cause protobuf@33.4, protobuf@32.1)
│           │   └───bazel_skylib@1.0.3 # (to 1.9.2, cause <root>)
...
    └───rules_swift@1.16.0
        └───rules_cc@0.0.2 (to 0.2.26, cause apple_support@2.10.1)
            └───bazel_skylib@1.0.3 # (to 1.9.2, cause <root>)

Mana MVS'ning jonli ishi. Transitive modullar bazel_skylib'ning juda eski versiyalarini (1.0.3, 1.4.1, 1.7.1, ...) so'rashgan, lekin hammasi 1.9.2'ga ko'tarilgan — va sabab aniq yozilgan: (to 1.9.2, cause <root>). Ya'ni biz (root module) MODULE.bazel'da bazel_dep(name = "bazel_skylib", version = "1.9.2") deb yozganimiz uchun. Boshqa joyda esa (cause rules_swift@4.2.1) — ya'ni rules_swift yuqoriroq versiyani talab qilgani uchun ko'tarilgan.

Bzlmod dependency'larni hal qilish bosqichlari: MODULE.bazel'dan BCR registry orqali to'liq grafga, MVS bilan versiya tanlashdan MODULE.bazel.lock'gacha; o'ng tomonda bazel_skylib uchun uchta turli so'rovdan eng yuqorisi tanlanishi

Diagrammaning o'ng tomoni — bizning haqiqiy misolimiz: root 1.9.2'ni, rules_apple 1.8.2'ni, rules_swift 1.7.1'ni so'radi — MVS eng yuqorisini, 1.9.2'ni tanladi.

📊 Real raqamlar: Bizning 4 qatorlik MODULE.bazel'imiz 167 package va 50'dan ortiq modul olib keldi. Ularning har biri bazel_skylibga bog'liq, lekin diskda bazel_skylibning bitta nusxasi bor (1.9.2). MVS tufayli "dependency hell" — turli versiyalar to'qnashuvi — Bazel'da deyarli yo'q. (M4 Pro, Bazel 9.3.0, 2026-10-13.)

Yanked (chaqirib olingan) versiyalar

Registry ba'zi versiyalarni yanked deb belgilashi mumkin — masalan, xavfsizlik zaifligi tufayli. Agar MVS yanked versiyani tanlasa, Bazel xato beradi. Yechish: yangiroq (yanked bo'lmagan) versiyaga o'ting, yoki aniq ruxsat bering — --allow_yanked_versions. Bu — supply chain xavfsizligining bir qismi: zararli yoki buzuq versiya chiqarilsa, registry uni "yanked" deb belgilaydi va sizning build'ingiz uni jim-jit ishlatib yubormaydi.


5. bazel mod bilan grafni o'rganish {#mod}

Katta loyihada dependency grafi ulkan bo'ladi. "Nega bizda rules_python bor?", "Bu protobuf qayerdan keldi?", "Qaysi versiya tanlandi?" — bu savollarga bazel mod buyrug'i javob beradi (external/mod-command). Eng foydali subkomandalar:

bazel mod graph                       # butun grafni daraxt ko'rinishida
bazel mod graph --include_unused --verbose   # + almashtirilgan versiyalar sababi bilan
bazel mod deps <modul>                # bitta modulning to'g'ridan-to'g'ri bog'liqliklari
bazel mod explain <modul>             # modul grafda qayerda va kim so'raydi
bazel mod show_repo <modul>           # modul qanday repo bilan ta'minlanadi (http_archive...)
bazel mod show_extension <ext>        # extension qaysi repo'larni yaratadi
bazel mod path <modul>                # root'dan modulga yo'l

bazel mod show_repo — ayniqsa foydali: u modul qayerdan, qanday yuklab olinishini aniq ko'rsatadi. Masalan, rules_swift:

bazel mod show_repo rules_swift
## rules_swift@4.2.1:
http_archive(
  name = "rules_swift+",
  urls = ["https://github.com/bazelbuild/rules_swift/releases/download/4.2.1/rules_swift.4.2.1.tar.gz"],
  integrity = "sha256-u86GfU3Hqj31vI/ECVjyxYQ5P/10x4IKBxRBXySGpag=",
  strip_prefix = "",
  remote_patches = {"https://bcr.bazel.build/modules/rules_swift/4.2.1/patches/module_dot_bazel_version.patch": "sha256-..."},
  remote_module_file_urls = ["https://bcr.bazel.build/modules/rules_swift/4.2.1/MODULE.bazel"],
  remote_module_file_integrity = "sha256-VQornucMgW3U4QaoXfPKuSxcqF3o9gYAEHXGjIMnAOY=",
  remote_patch_strip = 1,
)

Bu bizga aniq ko'rsatadi: rules_swift 4.2.1 aynan shu URL'dan yuklanadi, **aynan shu sha256** bilan tekshiriladi, va BCR uni bitta patch bilan qo'llaydi. Hech qanday sehr yo'q — har bir bayt hisobga olingan. Bu — "men bu kod qayerdan kelganini aniq bilaman" degani, bu esa supply chain xavfsizligining asosi.

💡 Pro tip: Grafni rasm sifatida ko'rish uchun: bazel mod graph --output graph | dot -Tsvg > graph.svg (brew install graphviz kerak). Katta loyihada bu "biz nimaga bog'liqmiz" savoliga eng tez javob.


6. MODULE.bazel.lock va reproduksiya {#lock}

Birinchi bazel build'dan keyin workspace ildizida yangi fayl paydo bo'ladi: MODULE.bazel.lock. Bu — Bazel'ning qulf (lockfile) fayli. U module resolution va extension'larni ishga tushirish natijasini saqlaydi (external/lockfile).

Bizning loyihamizda u 521 qator chiqdi. Boshi shunday:

{
  "lockFileVersion": 28,
  "registryFileHashes": {
    "https://bcr.bazel.build/bazel_registry.json": "...",
    "https://bcr.bazel.build/modules/rules_swift/4.2.1/MODULE.bazel": "...",
    "https://bcr.bazel.build/modules/rules_swift/4.2.1/source.json": "...",
    ...
  }
}

Lock fayl ikki asosiy narsani saqlaydi:

  1. **registryFileHashes** — resolution paytida o'qilgan har bir registry faylining (har modulning MODULE.bazel va source.json'i) hash'i. Resolution algoritmi bir xil input'larda to'liq deterministik bo'lgani uchun, bu hash'lar natijani to'liq qaytariladigan qiladi.
  2. **moduleExtensions** — har bir module extension (masalan, keyin ko'radigan swift_deps)ning input va natijasi.

🧠 Esda tuting: MODULE.bazel.lock'ni git'ga commit qiling. .bazelversion bilan birga (Bazelisk o'qiydigan), lock fayl butun jamoada va CI'da aynan bir xil dependency grafini kafolatlaydi. package-lock.json yoki Package.resolved nima qilsa — shu.

--lockfile_mode flagi lock fayl bilan munosabatni boshqaradi:

  • update (default) — yetishmayotgan ma'lumotni qo'shadi;
  • error — agar biror narsa lock bilan mos kelmasa, xato beradi va tarmoqqa chiqmaydi (CI uchun ideal: "lock faylga tegilmagan bo'lsin");
  • off — lock fayl tekshirilmaydi ham, yangilanmaydi ham.

CI'da --lockfile_mode=error qo'ying: agar kimdir MODULE.bazel'ni o'zgartirib, lock faylni yangilashni unutsa — CI darrov ushlaydi.

💡 Pro tip: Lock fayl merge conflict berishi mumkin. Bazel buning uchun maxsus git merge driver beradi (.gitattributes'ga MODULE.bazel.lock merge=bazel-lockfile-merge). Yoki sodda yo'l: konfliktda git checkout MODULE.bazel.lock bilan eski holatga qaytaring, MODULE.bazel'dagi konfliktni hal qiling, so'ng bazel mod deps bilan lock faylni qayta yarating.


7. Override'lar — dependency'ni o'z qo'lingizga olish {#override}

Ba'zan registry'dagi versiya sizga to'g'ri kelmaydi: patch qo'shishingiz, yangi (hali BCR'da yo'q) versiyani sinashingiz, yoki dependency'ni lokal nusxada tahrirlashingiz kerak. Buning uchun override'lar bor. Muhim qoida: faqat root module'ning override'lari ishlaydi — agar sizning modulingiz boshqa birovga dependency bo'lsa, sizning override'laringiz e'tiborsiz qoldiriladi (external/module).

Beshta override turi bor. Avval ularni jadval bilan, keyin ikkitasini amalda:

OverrideNima qiladiQachon
single_version_overrideBitta versiyaga qotiradi (pin), registry'ni o'zgartiradi yoki patch qo'yadiTransitive dependency'ni aniq versiyaga qotirish
multiple_version_overrideBir modulning bir nechta versiyasini birga yashashga ruxsat beradiKamdan-kam; migratsiya paytida
archive_overrideModulni registry o'rniga URL'dagi arxivdan oladiFork yoki hali chiqmagan versiya
git_overrideModulni git repo'dan (commit bo'yicha) oladiUpstream'ning aniq commit'ini sinash
local_path_overrideModulni lokal diskdagi papkadan oladiDependency'ni lokal tahrirlash / debug

single_version_override va bir real xato

Avval bazel_skylib'ni past versiyaga qotirishga urinib ko'raylik:

# MODULE.bazel (qo'shimcha)
single_version_override(
    module_name = "bazel_skylib",
    version = "1.8.0",
)
bazel mod graph
ERROR: module 'bazel_skylib' is overridden to use version '1.8.0',
which is lower than the version '1.9.2' requested by the root module

Mana foydali dars: single_version_override **root module o'zi bazel_dep bilan so'ragan versiyadan pastga** tusha olmaydi. Ya'ni biz bazel_dep(... version = "1.9.2") deb yozgan ekanmiz, override uni 1.8.0'ga tushira olmaydi. Mantiqiy: override — bu "men bu versiyani xohlayman" degani, lekin root'ning o'z bazel_dep'i ham bir talab. Ularni kelishtirish kerak. Agar chindan past versiya kerak bo'lsa — bazel_dep'dagi versiyani ham tushiring.

single_version_overridening haqiqiy foydasi — transitive dependency'ni (siz to'g'ridan-to'g'ri so'ramagan modulni) qotirishda, yoki unga patch qo'yishda:

# Transitive dependency'ni aniq versiyaga qotirish (xavfsizlik patch'i chiqqanda)
single_version_override(
    module_name = "protobuf",
    version = "34.0.bcr.1",
)

# Yoki: patch qo'yish
single_version_override(
    module_name = "some_dep",
    patches = ["//patches:fix.patch"],
    patch_strip = 1,
)

local_path_override — dependency'ni lokal tahrirlash (tekshirilgan)

Eng amaliy override — bu local_path_override. Tasavvur qiling: rules_swift'da bug bor va siz uni tuzatmoqchisiz, yoki o'zingizning ruleset'ingizni loyiha yonida ishlab chiqyapsiz. Buni ko'rsatish uchun kichik lokal modul yasadim:

# ../greet_rules/MODULE.bazel
module(name = "greet_rules", version = "0.0.0")

bazel_dep(name = "bazel_skylib", version = "1.9.2")
# ../greet_rules/defs.bzl — lokal tahrirlanadigan macro
def banner(name, text, visibility = None):
    native.genrule(
        name = name,
        outs = [name + ".txt"],
        cmd = "echo '=== %s ===' > $@" % text,
        visibility = visibility,
    )

Endi uni root MODULE.bazel'da registry o'rniga lokal papkadan olamiz:

# MODULE.bazel (qo'shimcha)
bazel_dep(name = "greet_rules", version = "0.0.0")
local_path_override(
    module_name = "greet_rules",
    path = "../greet_rules",
)
# BUILD.bazel
load("@greet_rules//:defs.bzl", "banner")

banner(
    name = "welcome",
    text = "Bazel 7: dependencylar",
)
bazel build //:welcome && cat bazel-bin/welcome.txt
INFO: Build completed successfully, 2 total actions
=== Bazel 7: dependencylar ===

Ishladi. bazel mod show_repo greet_rules buni tasdiqlaydi — modul endi registry'dan emas, lokal papkadan olinmoqda:

## greet_rules@_:
local_repository(
  name = "greet_rules+",
  path = "../greet_rules",
)

Diqqat qiling: canonical nom greet_rules@_ — bu _ non-registry override belgisidir (versiya o'rniga). Endi ../greet_rules/defs.bzl'ni tahrirlasangiz, keyingi build darrov yangi kodni oladi — hech narsa qayta yuklab olinmaydi. Aynan shu usul bilan rules_swift_package_manager o'z misollarini sinaydi: local_path_override(module_name = "rules_swift_package_manager", path = "../..").

⚠️ Diqqat: git_override va archive_override tarmoqqa chiqadi (git clone yoki arxiv yuklash). archive_override'da integrity = "sha256-..." berib, yuklangan fayl to'g'riligini kafolatlang. Bu override'lar fork yoki hali-BCR'da-yo'q versiya uchun; lekin ularni vaqtincha deb bilib, imkoni bo'lsa BCR versiyasiga qayting — override'lar lock faylni va reproduksiyani murakkablashtiradi.


8. SPM paketlarini ulash: rules_swift_package_manager {#spm}

Mana darsning eng amaliy qismi. iOS ekotizimining aksariyati Bazel moduli emas — ular Swift Package Manager paketlari: SnapKit, Alamofire, GRDB, swift-collections, Kingfisher va yuzlab boshqalar. Bazel bulardan qanday foydalanadi?

BCR'da ularning ko'pi yo'q. Shuning uchun alohida ko'prik bor: **rules_swift_package_manager** (qisqasi RSPM; muallif: cgrindel). U Package.swift va Package.resolved fayllaringizni o'qiydi, har bir paketni yuklab oladi, va uni rules_swift + rules_apple bilan Bazel target'lariga aylantiradi (github.com/cgrindel/rules_swift_package_manager).

🧠 Esda tuting: RSPM 1.25.0 faqat Bzlmod bilan ishlaydi. Bazel 9'da bu muammo emas — Bzlmod har doim yoqilgan. (RSPM README'da common --enable_bzlmod qatori bor, lekin Bazel 9.3.0'da bu no-op — biz lokal tekshirdik: --enable_bzlmod ham, --noenable_bzlmod ham xatosiz qabul qilinadi. Ya'ni bu qatorni yozishingiz shart emas.)

Keling, qadam-baqadam. Biz "Vazifalar" (tasklar) iOS ilovasiga Apple'ning rasmiy swift-collections paketini ulaymiz va undagi OrderedSet bilan dublikatsiz, tartibli ro'yxat yasaymiz.

1-qadam: MODULE.bazel'da extension'ni yoqish

# MODULE.bazel
module(name = "spm_demo", version = "1.0.0")

bazel_dep(name = "apple_support", version = "2.10.1")
bazel_dep(name = "rules_swift", version = "4.2.1")
bazel_dep(name = "rules_apple", version = "5.3.0")
bazel_dep(name = "rules_swift_package_manager", version = "1.25.0")

swift_deps = use_extension(
    "@rules_swift_package_manager//:extensions.bzl",
    "swift_deps",
)
swift_deps.from_package(
    resolved = "//:Package.resolved",
    swift = "//:Package.swift",
)
use_repo(
    swift_deps,
    # bu ro'yxatni hozircha bo'sh qoldiramiz —
    # `bazel mod tidy` keyin to'ldiradi
)

use_extension — swift_deps module extension'ini sahnaga olib keladi. from_package unga Package.swift va Package.resolved qayerdaligini aytadi. use_repo esa extension yaratgan repo'larni modulga ko'rinadigan qiladi — lekin qaysilarini aniq, buni biz qo'lda yozmaymiz, bazel mod tidy hal qiladi.

2-qadam: ildizda BUILD.bazel va Package.swift

swift_deps extension //:Package.resolved'ni o'qiy olishi uchun ildiz papka package bo'lishi kerak — ya'ni unda BUILD.bazel bo'lishi shart:

# BUILD.bazel (ildiz)
exports_files(["Package.swift", "Package.resolved"])

⚠️ Diqqat: Agar ildizda BUILD.bazel bo'lmasa, aniq xato olasiz: Unable to load package for //:Package.resolved: BUILD file not found. Yechim — ildizda (bo'sh bo'lsa ham) BUILD.bazel yarating. Bu FACTS'da ham ushlangan real xato.

// Package.swift
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "spm_demo",
    dependencies: [
        .package(url: "https://github.com/apple/swift-collections", from: "1.1.0"),
    ]
)

3-qadam: Package.resolved'ni yaratish

Package.resolved — aniq versiya va git commit'ini qulflaydigan fayl. Uni tizim swift bilan yaratamiz:

swift package resolve
Fetching https://github.com/apple/swift-collections
Computed https://github.com/apple/swift-collections at 1.7.3 (7.26s)
Working copy of https://github.com/apple/swift-collections resolved at 1.7.3

Hosil bo'lgan Package.resolved:

{
  "pins": [
    {
      "identity": "swift-collections",
      "kind": "remoteSourceControl",
      "location": "https://github.com/apple/swift-collections",
      "state": {
        "revision": "35a1edcb8ebd2bfe251d587f0b69e62af28f508c",
        "version": "1.7.3"
      }
    }
  ],
  "version": 2
}

Diqqat: from: "1.1.0" deb yozdik, lekin SPM eng yangi mos versiyani — 1.7.3'ni — tanladi va uni revision (aniq git commit) bilan qulfladi.

4-qadam: bazel mod tidy — use_repo'ni to'ldirish

Endi Bazel'ga "qaysi repo'larni ko'rsatish kerak"ligini aytamiz. Buni qo'lda yozish shart emas — bazel mod tidy avtomatik to'ldiradi:

bazel mod tidy
INFO: Updated use_repo calls for @rules_swift_package_manager//:extensions.bzl%swift_deps

Natijada MODULE.bazel'imizning oxiri shunday bo'ldi (bazel mod tidy o'zi yozdi):

use_repo(
    swift_deps,
    "swift_package",
    "swiftpkg_swift_collections",
)

Ikki repo qo'shildi:

  • **swiftpkg_swift_collections** — swift-collections paketining Bazel repo'si. Nom qoidasi: swiftpkg_ + paket identity, kichik harflarda, tinish belgilari _ bilan almashtirilgan (apple/swift-nio → swiftpkg_swift_nio).
  • **swift_package** — swift package buyruqlarini Bazel ichidan ishga tushirish uchun yordamchi repo (default yoqilgan).

💡 Pro tip: Eski qo'llanmalarda use_repo(swift_deps, "swift_deps_info", ...) deb yoziladi. swift_deps_info — faqat Gazelle plugin (BUILD fayllarini avtomatik generatsiya qilish) uchun kerak va u declare_swift_deps_info = True bilan yoqiladi. Biz Gazelle ishlatmaymiz, shuning uchun bazel mod tidy uni o'zi olib tashladi. Ishonmang — bazel mod tidy'ga ishoning: u aniq kerakli repo'larni yozadi.

5-qadam: qaysi target'ni ulashni topish

swift-collections bir nechta modul (product) beradi. Qaysi label kerakligini bazel query bilan ko'ramiz:

bazel query '@swiftpkg_swift_collections//:all'
@swiftpkg_swift_collections//:Collections
@swiftpkg_swift_collections//:DequeModule
@swiftpkg_swift_collections//:HeapModule
@swiftpkg_swift_collections//:OrderedCollections
@swiftpkg_swift_collections//:BitCollections
...

Bizga OrderedSet kerak, u OrderedCollections modulida. Demak label — @swiftpkg_swift_collections//:OrderedCollections.

6-qadam: ilovada ishlatish

// App/Sources/TaskStore.swift
import Foundation
import OrderedCollections

// OrderedSet tartibni saqlaydi VA dublikatni tushiradi — ro'yxatga allaqachon
// bor vazifani qayta qo'shib bo'lmaydi.
@Observable
final class TaskStore {
    private(set) var tasks: OrderedSet<String> = ["Nonushta", "Mashq", "Kod yozish"]

    func add(_ title: String) {
        let trimmed = title.trimmingCharacters(in: .whitespaces)
        guard !trimmed.isEmpty else { return }
        tasks.append(trimmed)   // dublikat bo'lsa, OrderedSet uni e'tiborsiz qoldiradi
    }

    func remove(_ title: String) {
        tasks.remove(title)
    }
}
// App/Sources/ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var store = TaskStore()
    @State private var yangi = ""

    var body: some View {
        NavigationStack {
            List {
                ForEach(Array(store.tasks), id: \.self) { task in
                    Text(task)
                }
            }
            .navigationTitle("Vazifalar")
            .safeAreaInset(edge: .bottom) {
                HStack {
                    TextField("Yangi vazifa", text: $yangi)
                        .textFieldStyle(.roundedBorder)
                    Button("Qo'shish") {
                        store.add(yangi)
                        yangi = ""
                    }
                }
                .padding()
            }
        }
    }
}

Va App/BUILD.bazel'da dependency'ni ulaymiz:

# App/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")
load("@rules_apple//apple:ios.bzl", "ios_application")

swift_library(
    name = "AppLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "AppLib",
    deps = [
        "@swiftpkg_swift_collections//:OrderedCollections",
    ],
)

ios_application(
    name = "Vazifalar",
    bundle_id = "uz.azimjondev.vazifalar",
    families = ["iphone"],
    infoplists = ["Info.plist"],
    minimum_os_version = "17.0",
    deps = [":AppLib"],
)

E'tibor bering: SPM dependency oddiy deps = ["@swiftpkg_.../..."] sifatida ulanadi — xuddi o'zimizning //Core kabi. Bazel uchun tashqi paket ham, bizning modulimiz ham — bir xil fuqaro.

bazel build //App:Vazifalar
[343 / 354] Compiling Swift module @swiftpkg_swift_collections//:OrderedCollections.rspm.__impl; 1s disk-cache, worker
[345 / 354] Compiling Swift module //App:AppLib; 1s disk-cache, worker
[351 / 354] Bundling Vazifalar
[352 / 354] Processing and signing Vazifalar
Target //App:Vazifalar up-to-date:
  bazel-bin/App/Vazifalar.ipa
INFO: Build completed successfully, 354 total actions

Vazifalar.ipa tayyor, swift-collections ichida kompilyatsiya qilingan holatda.

SPM ko'prigi ichida nima bo'lyapti

SPM paketlari Bazel'ga qanday ulanadi: Package.swift va Package.resolved fayllaridan swift_deps module extension orqali swiftpkg_swift_collections repo'siga, undan esa ilovaning swift_library deps'iga; pastda uch bosqichli ish tartibi

Diagramma oqimni ko'rsatadi: ikki manba fayl (Package.swift — nima kerak; Package.resolved — aniq versiya) → swift_deps extension ularni o'qiydi → @swiftpkg_swift_collections repo'sini yaratadi (ichida tayyor swift_library'lar) → siz uni deps'ga qo'shasiz.

Har paket qo'shilganda uchta fayl yangilanadi

Bu — RSPM'ning halol narxi. Har safar paket qo'shsangiz yoki versiyasini o'zgartirsangiz, uchta fayl o'zgaradi:

  1. Package.swift — yangi .package(...) qatori;
  2. Package.resolved — yangi versiya/revision (swift package resolve yoki bazel run @swift_package//:resolve);
  3. MODULE.bazel — yangi use_repo repo'si (bazel mod tidy).

Paketni yangilash uchun RSPM yordamchi target beradi:

bazel run @swift_package//:update              # hamma paketni yangilash
bazel run @swift_package//:update -- SomePackage   # bittasini
bazel run @swift_package//:resolve             # faqat Package.resolved'ni yangilash

So'ng yana bazel mod tidy. Uchala faylni ham git'ga commit qiling.

O'lchov: dependency qancha turadi?

Eng muhim savol: tashqi paket build'ni qancha sekinlashtiradi? O'lchadim (M4 Pro 12-core 24 GB, Xcode 27.1, Bazel 9.3.0, RSPM 1.25.0, swift-collections 1.7.3; har o'lchov eksklyuziv build-slot bilan):

HolatVaqtNima bo'ldi
Birinchi marta (cold, cache butunlay o'chiq)73.1 sHammasi noldan: rules_swift ishchi tooli (abseil'dan, bir martalik), swift-collections va ilova
Yangi checkout + umumiy disk cache19.1 s188 action disk cache'dan; faqat fetch + analysis + link qayta
Server issiq + disk cache (qayta build)2.2 s354 action'ning hammasi cache'dan
Bitta ilova faylini tahrirlab qayta build5.1 sFaqat AppLib qayta kompilyatsiya + bundle; swift-collections qayta kompilyatsiya qilinmaydi

📊 Real raqamlar: Eng muhim qatori — oxirgisi. Siz ilova kodingizni o'zgartirganda (ContentView.swift), Bazel faqat 2 ta action bajardi: AppLib'ni qayta kompilyatsiya qildi va ipa'ni qayta yig'di. swift-collections qayta kompilyatsiya qilinmadi — u o'zgarmagani uchun cache'dan keldi. Ana shu — Bazel'ning asosiy ustunligi: dependency bir marta kompilyatsiya qilinadi, keyin hech qachon (o'zi o'zgarmaguncha) qayta tegilmaydi. Birinchi 73 soniyaning katta qismi esa swift-collections emas, rules_swiftning ishchi toolini (persistent worker) abseil'dan bir marta kompilyatsiya qilish — bu har mashina/cache uchun bir martalik narx.

🏢 Real-world: BuildBuddy'ning iOS→Bazel migratsiya qo'llanmasida (Brentley Jones, 2024, Mastodon forki) SPM dependency'lar aynan shu yo'l bilan — rules_swift_package_manager + Gazelle orqali — olib kiriladi. Katta loyihalarda (masalan Toss, ~1000 modul) jamoalar uchinchi tomon dependency'larini alohida repoga ko'chirishni va grafni "cache'ga yaroqli" holatga keltirishni tavsiya qiladi (Tuist case study, 2026-10-01) — chunki qancha dependency barqaror bo'lsa, cache shuncha samarali.


9. Ko'prik ichida nima bor: module extension va use_repo {#extension}

8-bo'limda use_extension, from_package, use_repo so'zlarini sehr kabi ishlatdik. Endi parda ortiga qaraymiz, chunki bu mexanizm — Bzlmod'ning eng kuchli qismi va uni tushunsangiz, istalgan ruleset'ning dependency tizimini o'qiy olasiz.

Module extension — bu Bzlmod tizimini kengaytirish usuli (external/extension). bazel_dep faqat Bazel modullari (BCR'dagilar) uchun. Lekin SPM paketlari Bazel moduli emas — ularni yuklab olish, Package.resolved'ni o'qish, har bir paketni swift_library'ga aylantirish kerak. Buni oddiy bazel_dep qila olmaydi. Module extension esa fayl o'qishi, tarmoqqa chiqishi va repo yaratishi mumkin — xuddi swift_deps qilgandek.

Mexanizm uch qismdan iborat:

  1. **use_extension** — extension'ni sahnaga oladi:
swift_deps = use_extension("@rules_swift_package_manager//:extensions.bzl", "swift_deps")

Bu rules_swift_package_manager modulidagi extensions.bzl faylida aniqlangan swift_deps nomli extension'ni qaytaradi.

  1. Tag'lar — extension'ga ma'lumot uzatasiz. swift_deps.from_package(...) — bu "tag". Extension muallifi tag_class bilan tag'ning sxemasini belgilaydi (qanday atributlar bo'lishi mumkin). Bizning holatda tag resolved va swift atributlarini oladi.
  2. **use_repo — extension yaratgan repo'larni modulingizga ko'rinadigan** qiladi:
use_repo(swift_deps, "swiftpkg_swift_collections")

Extension o'nlab repo yaratishi mumkin, lekin siz faqat use_repo'da sanaganlaringizga label bilan murojaat qila olasiz. bazel mod tidy aynan shu ro'yxatni to'g'rilaydi.

Extension'lar dangasa (lazy) ishga tushadi: repo'si ishlatilmasa, extension umuman ishga tushmaydi. bazel mod show_extension bilan extension qaysi repo'larni yaratishini ko'rishingiz mumkin.

🧠 Esda tuting: Bu — "qanday qilib Bazel SPM'ni ham, Maven'ni ham, PyPI'ni ham, Go modullarni ham tushunadi?" degan savolning javobi. Har bir ekotizim uchun bitta module extension yoziladi: rules_jvm_external'da maven, rules_python'da pip, rules_swift_package_manager'da swift_deps. Hammasi bir xil mexanizm — extension fayl o'qiydi, tashqi paket menejeri bilan gaplashadi, repo yaratadi. 17-darsda o'z rule'laringizni yozganimizda bu mexanizmni yana ko'ramiz.


10. Tayyor binary'lar: xcframework import {#xcframework}

Ba'zan sizga manba kodi yo'q — sotuvchi (vendor) sizga faqat tayyor, kompilyatsiya qilingan binary beradi: analitika SDK, to'lov SDK, ba'zan ichki jamoadagi boshqa bo'limning kutubxonasi. Apple'ning bu uchun formati — **.xcframework** (bir nechta platforma/arxitektura uchun framework'larni bitta paketga jamlaydi: iOS device, iOS simulator, macOS...).

Bazel bunday tayyor binary'ni ikki rule bilan import qiladi (rules_apple, rules-apple.md):

  • **apple_static_xcframework_import** — statik kutubxonali xcframework (ilovaga linklanadi, alohida fayl qolmaydi);
  • **apple_dynamic_xcframework_import** — dinamik framework (ilova ichida .framework sifatida qoladi).

Avval bir xcframework yasaymiz (vendor tomoni)

Import qilish uchun bizga haqiqiy .xcframework kerak. Uni ham Bazel yasay oladi — apple_static_xcframework rule'i bilan (vendor sizga aynan shunday yasab beradi):

// MathKit/Sources/Math.swift
import Foundation

// "Sotuvchi" bizga shu kutubxonani yopiq binary sifatida yetkazadi.
public struct Valyuta {
    public init() {}
    public func qoshimcha(_ summa: Decimal, foiz: Decimal) -> Decimal {
        summa + (summa * foiz / 100)
    }
}
# MathKit/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")
load("@rules_apple//apple:apple.bzl", "apple_static_xcframework")

swift_library(
    name = "MathKitLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "MathKit",
)

# Vendor tomoni: koddan tarqatish uchun .xcframework yasaydi
apple_static_xcframework(
    name = "MathKit",
    bundle_name = "MathKit",
    ios = {
        "simulator": ["arm64"],
    },
    minimum_os_versions = {"ios": "17.0"},
    deps = [":MathKitLib"],
)
bazel build //MathKit:MathKit
Target //MathKit:MathKit up-to-date:
  bazel-bin/MathKit/MathKit.xcframework.zip
INFO: Elapsed time: 38.627s, Critical Path: 6.50s

Natija — MathKit.xcframework.zip. Uni ochib ko'rsak, ichida kutilgan struktura bor:

MathKit.xcframework/
├── Info.plist                          (XCFrameworkFormatVersion 1.0)
└── ios-arm64-simulator/
    └── MathKit.framework/
        ├── MathKit                     (statik binary)
        └── Modules/
            └── MathKit.swiftmodule/
                ├── arm64.swiftmodule   (Swift interfeys)
                └── arm64.swiftdoc

⚠️ Diqqat: Biz faqat simulator uchun yasadik (ios = {"simulator": ["arm64"]}), shuning uchun xcframework'da faqat ios-arm64-simulator slice bor. Haqiqiy vendor xcframework'ida odatda ikkala slice bo'ladi: ios-arm64 (device) va ios-arm64_x86_64-simulator. Agar device uchun build qilmoqchi bo'lsangiz, lekin xcframework'da device slice bo'lmasa — build xato beradi. Vendor'dan har doim kerakli slice'larni so'rang.

Endi uni import qilamiz (iste'molchi tomoni)

Faraz qilaylik, bu MathKit.xcframework bizga tashqaridan keldi va biz uni thirdparty/ papkaga qo'ydik. Import shunday:

# thirdparty/BUILD.bazel
load("@rules_apple//apple:apple.bzl", "apple_static_xcframework_import")

# Sotuvchidan kelgan tayyor binary. Biz uni kompilyatsiya qilmaymiz — faqat import qilamiz.
apple_static_xcframework_import(
    name = "MathKit",
    xcframework_imports = glob(["MathKit.xcframework/**"]),
    has_swift = True,   # Swift kodi bor — swiftmodule mavjud
    visibility = ["//visibility:public"],
)

xcframework_imports = glob(["MathKit.xcframework/**"]) — xcframework ichidagi hamma faylni rule'ga beradi. has_swift = True — Bazel'ga "bu framework'da Swift interfeys fayllari bor" deb aytadi (Objective-C/C xcframework'da bu False).

Endi ilova uni oddiy deps sifatida ishlatadi:

// App/Sources/ContentView.swift
import SwiftUI
import MathKit   // tayyor xcframework'dan keladi

struct ContentView: View {
    private let valyuta = Valyuta()

    var body: some View {
        let natija = valyuta.qoshimcha(100_000, foiz: 15)
        return VStack(spacing: 12) {
            Text("Soliq kalkulyatori").font(.headline)
            Text("100 000 + 15% = \(natija.description)").font(.title3)
        }
        .padding()
    }
}
# App/BUILD.bazel
load("@rules_swift//swift:swift.bzl", "swift_library")
load("@rules_apple//apple:ios.bzl", "ios_application")

swift_library(
    name = "AppLib",
    srcs = glob(["Sources/**/*.swift"]),
    module_name = "AppLib",
    deps = ["//thirdparty:MathKit"],   # xcframework import
)

ios_application(
    name = "Hisob",
    bundle_id = "uz.azimjondev.hisob",
    families = ["iphone"],
    infoplists = ["Info.plist"],
    minimum_os_version = "17.0",
    deps = [":AppLib"],
)

Xcframework'da faqat simulator slice bo'lgani uchun simulator arxitekturasi bilan build qilamiz:

bazel build //App:Hisob --ios_multi_cpus=sim_arm64
INFO: Analyzed target //App:Hisob (199 packages loaded, 6820 targets configured).
[338 / 347] Compiling Swift module //App:AppLib; 1s disk-cache, worker
[340 / 347] Linking App/Hisob_bin; 1s disk-cache, darwin-sandbox
[345 / 347] Processing and signing Hisob
Target //App:Hisob up-to-date:
  bazel-bin/App/Hisob.ipa
INFO: Elapsed time: 11.355s, Critical Path: 7.72s
INFO: Build completed successfully, 47 total actions

Ilova tayyor binary'ni ichiga oldi — manba kodisiz. AppLib import MathKit qildi, Valyuta struct'ini xcframework ichidagi arm64.swiftmodule'dan topdi, va linklash paytida statik binary ilovaga qo'shildi. Uchinchi usulni ham o'rgandik.


11. http_archive, integrity va supply chain xavfsizligi {#supply}

Ba'zan dependency na BCR modul, na SPM paket, na xcframework bo'ladi — shunchaki URL'dagi arxiv (masalan, ba'zi C kutubxona yoki boshqa fayl). Buning uchun http_archive repo rule'i bor. Bzlmod'da uni use_repo_rule bilan to'g'ridan-to'g'ri MODULE.bazel'da chaqirasiz:

# MODULE.bazel
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "some_lib",
    urls = ["https://example.com/some_lib-1.2.3.tar.gz"],
    integrity = "sha256-c9526390a7cd420fdcec2988b4f3626fe9c5b51e2959f685e8f4d170d1a9bd96=",
    strip_prefix = "some_lib-1.2.3",
    build_file = "//third_party:some_lib.BUILD",   # arxivda BUILD yo'q bo'lsa, o'zimiz beramiz
)

Bu yerda eng muhim atribut — **integrity** (yoki eski sha256). Bu — yuklangan arxivning kriptografik hash'i. Agar arxiv bir baytga bo'lsa ham o'zgarsa (xaker URL'ni almashtirsa, server buzilsa, man-in-the-middle hujum bo'lsa), hash mos kelmaydi va Bazel build'ni to'xtatadi.

Bu — supply chain xavfsizligining asosi. Keling, nega muhimligini aniq ko'raylik. bazel mod show_repo rules_swift natijasini eslang (5-bo'lim):

http_archive(
  name = "rules_swift+",
  urls = ["https://github.com/bazelbuild/rules_swift/releases/download/4.2.1/rules_swift.4.2.1.tar.gz"],
  integrity = "sha256-u86GfU3Hqj31vI/ECVjyxYQ5P/10x4IKBxRBXySGpag=",
  ...
)

Har bir modul — hatto BCR'dan kelganlar ham — aynan shu tarzda yuklanadi: URL + integrity. Ya'ni:

  • Hermetiklik: build siz e'lon qilgan aniq fayllardan hosil bo'ladi, tasodifiy "eng yangi" versiyadan emas.
  • Buzilishni aniqlash: agar GitHub Releases'dagi arxiv almashtirilsa, sizning build'ingiz darrov to'xtaydi.
  • Reproduksiya: hash MODULE.bazel.lock'ga yoziladi, demak boshqa mashinada ham aynan shu fayl ishlatiladi.

🚀 Motivatsiya: 2021-yildagi mashhur supply chain hujumlari (npm, PyPI'dagi zararli paketlar) ko'pincha "aynan shu versiyada aynan shu fayl ekanini hech kim tekshirmagani" tufayli yuz berdi. Bazel'da bu deyarli imkonsiz: har bir arxiv sha256 bilan qulflangan, har bir versiya lock faylda. BCR yana oldinga boryapti — "Provenance Attestations" (yuklangan paketning haqiqiy manbadan kelganini kriptografik tasdiqlash) ustida ishlanmoqda (external/overview).

💡 Pro tip: integrity qiymatini qayerdan olish kerak? Birinchi marta noto'g'ri (yoki bo'sh) qiymat bilan yozing — Bazel xatoda to'g'ri hash'ni ko'rsatadi, uni nusxalab qo'ying. Yoki: shasum -a 256 fayl.tar.gz va natijani sha256-<base64> formatiga o'tkazing. bazel mod buyruqlari BCR modullari uchun bu hash'ni avtomatik to'ldiradi.


12. Offline build: bazel vendor {#vendor}

Tasavvur qiling: internetsiz muhitda (xavfsiz CI, samolyotda, yoki tarmoq cheklangan korporativ server) build qilishingiz kerak. Yoki: dependency'ning manbasini aniq nazorat qilmoqchisiz — "GitHub o'chsa ham, bizning build'imiz ishlasin". Buning uchun vendor mode bor (external/vendor).

bazel vendor tashqi dependency'larni lokal papkaga nusxalaydi:

# Bitta repo'ni vendor qilish
bazel vendor --vendor_dir=vendor_src --repo=@bazel_skylib
INFO: Vendoring repositories...
INFO: All requested repos vendored successfully.

Natijada vendor_src/ papkasida (tekshirilgan):

vendor_src/
├── _registries/
│   └── bcr.bazel.build/        (registry fayllari — offline resolution uchun)
├── bazel_skylib+/              (repo'ning o'zi: BUILD, lib/, rules/...)
├── @bazel_skylib+.marker       (yangilik belgisi)
├── bazel-external -> .../external   (symlink)
└── VENDOR.bazel                (vendor konfiguratsiyasi)

Endi offline build qila olasiz:

bazel build --vendor_dir=vendor_src //App:Vazifalar

Agar barcha dependency'larni vendor qilmoqchi bo'lsangiz — bazel vendor --vendor_dir=vendor_src (argumentsiz). Lekin ogohlantirish: bu uzoq davom etadi va papka juda kattalashadi (butun transitive graf). Shuning uchun avval kerakli target'lar uchun vendor qiling: bazel vendor --vendor_dir=vendor_src //App:Vazifalar.

VENDOR.bazel faylida alohida repo'larni boshqarasiz:

  • pin("@@bazel_skylib+") — repo'ni vendored holatiga qotiradi (siz uni qo'lda tahrirlashingiz mumkin, Bazel ustiga yozmaydi);
  • ignore("@@rules_cc+") — repo'ni vendor'dan butunlay chiqaradi.

⚠️ Diqqat: vendor_src/bazel-external symlink'ini git'ga qo'shmang (.gitignore'ga qo'shing) — u lokal output base'ga ishora qiladi. Qolgan vendored manbani esa commit qilsangiz, boshqa mashinada internetsiz aynan shu build'ni olasiz.


13. Qaysi yo'lni qachon tanlash {#qaysi}

Uch usulni ko'rib chiqdik. Endi — qaysi birini qachon tanlash. Qoida sodda: manbaning turi usulni belgilaydi.

ManbaUsulMisol
Bazel'ni biladigan ruleset / kutubxona (BCR'da bor)bazel_deprules_swift, rules_apple, bazel_skylib, googletest
Ochiq kodli Swift/iOS paketi (SPM'da)rules_swift_package_managerSnapKit, Alamofire, swift-collections, GRDB, Kingfisher
Yopiq kodli, oldindan kompilyatsiya qilingan SDKapple_*_xcframework_importanalitika SDK, to'lov SDK, vendor .xcframework
URL'dagi oddiy arxiv (BUILD'siz)http_archive + build_fileba'zi C kutubxonalar

Amaliy qarorlar:

  • Birinchi tanlov — BCR moduli. Agar kutubxona BCR'da bo'lsa (registry.bazel.build'da qidiring), bazel_dep eng sodda va eng yaxshi integratsiyalashgan yo'l. MVS, lock fayl, bazel mod — hammasi tekin.
  • iOS paketlarining aksariyati — SPM orqali. Haqiqat shuki, iOS ekotizimining katta qismi BCR'da emas. Shuning uchun rules_swift_package_manager kundalik ishda eng ko'p ishlatiladigan yo'l bo'ladi. Halol narxi: har paket qo'shilganda uch fayl yangilanadi.
  • Faqat majbur bo'lsangiz — xcframework. Yopiq kodli binary debug qilish qiyinroq (manba yo'q), platforma/arxitektura slice'lariga bog'liq, va yangilanishni qo'lda kuzatasiz. Lekin vendor faqat binary bersa, boshqa yo'l yo'q.

Tashqi kodni kiritishning uchta yo'li va har birining mexanizmi, qachon ishlatilishi va misoli

Halol kamchiliklar

Har usulning o'z narxi bor — buni oldindan bilgan ma'qul:

UsulKamchilik
bazel_dep (BCR)Kutubxona BCR'da bo'lishi shart; ko'p iOS paketlari yo'q
SPM (RSPM)Har paketda 3 fayl (Package.swift + .resolved + MODULE.bazel); bazel mod tidy qadami; ba'zan generatsiya qilingan target'ni configure_package bilan tuzatish kerak
xcframeworkManba yo'q (debug qiyin); slice'larga bog'liq; yangilanish qo'lda; versiya qulfi qo'lda

🏢 Real-world: Rappi iOS jamoasi Bazel'ga o'tishda bitta uchinchi tomon dependency'sining private header'lari bir nechta CocoaPods pod orasida ulashilgani tufayli sandboxing'ni o'chirishga majbur bo'ldi (engineering.rappi.com). Bu — dependency bilan bog'liq real ishqalanish misoli: tashqi kod har doim ham Bazel'ning hermetiklik qoidalariga mos tushavermaydi, ba'zan patch yoki maxsus sozlama kerak bo'ladi. Shuning uchun dependency qo'shganda "ishladi" bilan to'xtamang — build hermetik va cache'ga yaroqli ekanini ham tekshiring.

⚠️ Diqqat: Dependency qancha ko'p bo'lsa, build grafi shuncha katta va cache shuncha "mo'rt" bo'ladi. Katta jamoalar (Toss, ~1000 modul) uchinchi tomon dependency'larini alohida repoga ajratadi va grafni barqaror ushlaydi — chunki tez-tez o'zgaradigan dependency cache'ni buzadi. "Kerak bo'lgani uchun" emas, "chindan kerak bo'lgani uchun" dependency qo'shing (YAGNI).


14. Amaliy mashqlar {#mashqlar}

Bu mashqlarni scratchpad/work/'dagi kabi alohida ish papkasida bajaring (repoga tegmang).

  1. Ikkinchi SPM paketini qo'shing. spm_demo ilovasiga yana bitta paket ulang — masalan swift-argument-parser yoki Alamofire. Package.swift'ga qo'shing → swift package resolve → bazel mod tidy → bazel query '@swiftpkg_<nom>//:all' bilan to'g'ri label'ni toping → deps'ga qo'shing → build. Qaysi uch fayl o'zgarganini tekshiring.
  2. Grafni tekshiring. bazel mod explain protobuf ishga tushiring va protobuf grafingizga qayerdan kelganini aniqlang (kim uni so'raydi?). So'ng bazel mod show_repo protobuf bilan u qaysi URL'dan, qaysi sha256 bilan yuklanishini toping.
  3. Lock faylni sinang. MODULE.bazel.lock'ni ochib, lockFileVersion va registryFileHashes'dagi bir nechta qatorni ko'ring. So'ng MODULE.bazel'da bir bazel_dep versiyasini o'zgartiring va lock fayl qanday yangilanganini (git diff) kuzating.
  4. **⭐ local_path_override bilan patch.** Kichik lokal modul yarating (greet_rules kabi), uni local_path_override bilan ulang, so'ng .bzl faylini tahrirlab, keyingi build darrov yangi kodni olishini ko'ring. bazel mod show_repo bilan canonical nom @_ bilan tugaganini tasdiqlang.
  5. ⭐ To'liq slice bilan xcframework. apple_static_xcframework'ni ham device, ham simulator uchun yasang (ios = {"simulator": ["arm64"], "device": ["arm64"]}). Unzip qilib, Info.plist'da ikkala slice borligini tekshiring. So'ng uni import qilib, device uchun ham (--ios_multi_cpus=arm64), simulator uchun ham build qiling.
  6. ⭐ Offline build. bazel vendor --vendor_dir=vendor_src //App:Vazifalar bilan dependency'larni vendor qiling. So'ng tarmoqni o'chirib (yoki --repository_cache= bilan cache'ni ham o'chirib), bazel build --vendor_dir=vendor_src //App:Vazifalar ishlashini ko'ring.

15. O'zingizni tekshiring {#checklist}

  • [ ] MODULE.bazel'da faqat to'g'ridan-to'g'ri bog'liqliklar yozilishini va transitive'lar avtomatik hal bo'lishini tushuntira olaman.
  • [ ] MVS nima qilishini aytaman: har modul uchun eng yuqori so'ralgan versiya tanlanadi.
  • [ ] bazel mod graph / explain / show_repo farqini bilaman va qaysi savolga qaysi biri javob berishini aytaman.
  • [ ] MODULE.bazel.lock nega git'ga commit qilinishini va --lockfile_mode=error CI'da nega foydali ekanini bilaman.
  • [ ] Beshta override turini sanay olaman va local_path_override qachon kerakligini aytaman.
  • [ ] SPM paketini ulashning to'liq oqimini yoddan bilaman: Package.swift → swift package resolve → bazel mod tidy → deps.
  • [ ] @swiftpkg_<identity> repo nomi qoidasini va product label'ini bazel query bilan topishni bilaman.
  • [ ] apple_static_xcframework_import va http_archive + integrity qachon ishlatilishini va sha256 nega supply chain xavfsizligi ekanini tushuntira olaman.

16. Xulosa {#xulosa}

  • Bazel'da tashqi kod — bu "qaysidir versiya yuklanib qoldi" emas: har bir paket aniq versiya + sha256 bilan e'lon qilinadi va MODULE.bazel.lock'da qulflanadi. Natija — qaytariladigan (reproducible) va hermetik build.
  • Bzlmod modul va repo tushunchalariga quriladi; Bazel ildiz MODULE.bazel'dan boshlab BCR registry orqali butun grafni topadi va MVS bilan har modul uchun bitta (eng yuqori so'ralgan) versiyani tanlaydi.
  • bazel mod graph/explain/show_repo grafni o'rganish uchun, MODULE.bazel.lock reproduksiya uchun, override'lar (ayniqsa local_path_override) dependency'ni nazorat qilish uchun.
  • iOS ekotizimining aksariyati SPM'da — **rules_swift_package_manager** ularni Package.swift/Package.resolved'dan o'qib, @swiftpkg_<identity> repo'lariga aylantiradi; oqim: resolve → bazel mod tidy → deps.
  • Yopiq binary'lar uchun xcframework import (apple_static_xcframework_import), oddiy arxivlar uchun **http_archive + integrity**.
  • O'lchov (M4 Pro): dependency birinchi marta kompilyatsiya qilinadi (cold 73 s), keyin disk cache bilan 2 s; eng muhimi — o'z kodingizni o'zgartirganda dependency qayta kompilyatsiya qilinmaydi (incremental 5 s, atigi 2 action).
  • Usulni manba belgilaydi: BCR → bazel_dep, SPM → RSPM, yopiq binary → xcframework. Har birining halol narxi bor; kerakdan ortiq dependency qo'shmang (YAGNI).

Keyingi dars: Bazel 8: Haqiqiy ilova anatomiyasi — resurslar, lokalizatsiya, widget extension, framework, entitlements va signing — endi dependency'larni ula olganimizdan so'ng, haqiqiy ilovaning ichki tuzilishiga — resurslar, lokalizatsiya, widget va signing'ga — o'tamiz.


17. Manbalar {#manbalar}

  1. Bazel — External dependencies overview — modul, repo, registry, repo rule tushunchalari.
  2. Bazel — Bazel modules (MVS, overrides) — versiya tanlash, single_version_override, local_path_override, git_override, archive_override.
  3. Bazel — Bazel registries (BCR) — index registry formati, source.json, --registry.
  4. Bazel — Bazel Lockfile — MODULE.bazel.lock, --lockfile_mode, merge conflict.

Markdown manba

iOS Lessons